I will show you some examples:
First example, do not return local scope object, for example:
const string &dontDoThis(const string &s)
{
    string local = s;
    return local;
}
You can't return local by reference, because local is destroyed at the end of the body of dontDoThis.
Second example, you can return by reference:
const string &shorterString(const string &s1, const string &s2)
{
    return (s1.size() < s2.size()) ? s1 : s2;
}
Here, you can return by reference both s1 and s2 because they were defined before shorterString was called.
Third example:
char &get_val(string &str, string::size_type ix)
{
    return str[ix];
}
usage code as below:
string s("123456");
cout << s << endl;
char &ch = get_val(s, 0); 
ch = 'A';
cout << s << endl; // A23456
get_val can return elements of s by reference because s still exists after the call.
Fourth example
class Student
{
public:
    string m_name;
    int age;    
    string &getName();
};
string &Student::getName()
{
    // you can return by reference
    return m_name;
}
string& Test(Student &student)
{
    // we can return `m_name` by reference here because `student` still exists after the call
    return stu.m_name;
}
usage example:
Student student;
student.m_name = 'jack';
string name = student.getName();
// or
string name2 = Test(student);
Fifth example:
class String
{
private:
    char *str_;
public:
    String &operator=(const String &str);
};
String &String::operator=(const String &str)
{
    if (this == &str)
    {
        return *this;
    }
    delete [] str_;
    int length = strlen(str.str_);
    str_ = new char[length + 1];
    strcpy(str_, str.str_);
    return *this;
}
You could then use the operator= above like this:
String a;
String b;
String c = b = a;