跳转至

C++类和对象相关内容

约 3824 个字 972 行代码 预计阅读时间 25 分钟

explicit关键字

在C++中,给类对象初始化时会调用类的构造函数,但是也可以使用赋值运算符为构造函数只有一个参数(或者只有一个参数没有缺省值)的类对象赋值,如下面代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test(int num)
        :_num(num)
    {}

    void print()
    {
        cout << _num << endl;
    }
};

int main()
{
    test t(1);//直接调用构造函数进行对象实例化
    test t1 = 1;

    t.print();
    t1.print();

    return 0;
}
输出结果
1
1

在上面的代码中,test类对象初始化时需要调用有一个参数的构造函数(对应test t(1)),而也可以直接使用赋值运算符,将初始化值赋值给类对象,这个过程经历了:调用构造函数使用整型1为临时对象初始化,再调用拷贝构造函数将临时对象拷贝给t1对象,这个过程也是一种类型转换,但是实际上这个过程一般会被编译器优化为直接调用构造函数,使用整型1对类对象初始化,即优化过程:构造函数+拷贝构造函数->构造函数

而如果不想以上面的方式,只用直接调用构造函数的方式对类对象进行初始化时,可以使用explicit关键字对构造函数进行修饰

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    explicit test(int num)
        :_num(num)
    {}

    void print()
    {
        cout << _num << endl;
    }
};

int main()
{
    test t(1);//直接调用构造函数进行对象实例化
    test t1 = 1;

    t.print();
    t1.print();

    return 0;
}
报错信息
不存在从 "int" 转换到 "test" 的适当构造函数

当构造函数被explicit关键字修饰后,test t1 = 1的初始化方式失效

在C++11标准规范中,也支持对不只有一个参数的构造函数使用对类对象进行赋值初始化的方式

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
    int _num1;
public:
    test(int num, int num1)
        :_num(num)
        ,_num1(num1)
    {}

    void print()
    {
        cout << _num << " " << _num1 << endl;
    }
};

int main()
{
    test t(2, 3);
    test t1 = { 2,4 };

    t.print();
    t1.print();
    return 0;
}
输出结果
2 3
2 4

同样,如果不愿意使用直接赋值的方式为类对象进行初始化时,可以使用explicit关键字修饰构造函数

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
    int _num1;
public:
    explicit test(int num, int num1)
        :_num(num)
        ,_num1(num1)
    {}

    void print()
    {
        cout << _num << " " << _num1 << endl;
    }
};

int main()
{
    test t(2, 3);
    test t1 = { 2,4 };

    t.print();
    t1.print();
    return 0;
}
报错信息
"test" 的复制列表初始化不能使用显式构造函数

但是,如果为已经实例化的对象再次赋值时,则会调用赋值运算符重载函数为对象赋值,如下面代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test(int num)
        :_num(num)
    {}

    void print()
    {
        cout << _num << endl;
    }

    test(const test& d)
    {
        _num = d._num;
    }

    test& operator=(const test& t)
    {
        if (this != &t)
        {
            _num = t._num;
        }
        return *this;
    }
};

int main()
{
    test t(1);//为对象初始化
    t = 2;
    return 0;
}

在上面的代码中,t对象再次赋值时会调用赋值重载,首先会调用拷贝构造函数,但是会被编译器优化为直接调用构造函数,再调用赋值重载函数

static成员

static成员的介绍

当需要统计一个类创建了多少个对象时,第一反应是创建一个全局变量,当每一次调用构造函数或者拷贝构造函数时,就让其进行+1操作,但是这个思路的问题是该全局变量不仅是在类中可以访问,也可以在类外访问,此时如果在类外改变了该变量的值,那么此时的计数不一定准确,如果直接放置到类内作为类的成员变量,那么每一个类对象在创建时都会为这个成员变量分配独立的空间,那么每一个类对象的计数器变量都只为1,并没有达到计数的效果

为了解决上面的问题,C++支持在类中创建静态成员变量,该成员变量不属于任何一个实例对象,而属于整个类,但是因为在类内,所以类中的成员函数也可以直接访问该静态成员变量。一样的思路,当每一次调用构造函数或者拷贝构造函数时让该变量+1即可实现统计一个类有多少个对象,并且这个方法的好处是保证了类的封装性,如果该成员变量具有private属性,那么在类外也不可以直接修改(要修改时需要类中提供静态set成员函数),但是此时要在类外直接访问该静态成员变量时需要提供get函数,如下面的代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
using namespace std;

class test
{
private:
    //静态成员变量
    static int _count;//静态成员变量声明
public:
    test()
    {
        _count++;
    }

    test(const test& t)
    {
        _count++;
    }

    //使用静态成员函数在类外访问静态成员变量
    static int getCount()
    {
        return _count;
    }
};

int test::_count = 0;//静态成员变量初始化

int main()
{
    test t;
    test t1;
    test t3(t1);
    //统计创建的对象的个数
    cout << test::getCount() << endl;
    return 0;
}
输出结果
3

上面代码的作用是统计创建类对象的个数,基本思路是通过统计调用构造函数或者拷贝构造函数的次数来统计类对象的个数,类中存在一个静态成员变量_count,该成员变量从属于整个类而不是某一个单独的类对象,因为是静态成员变量,所以需要使用静态成员函数才可以在类域外访问(因为具有private属性,所以不可以直接通过类名进行调用)

static成员的使用

在C++中,声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量

同样,用static修饰的成员函数,称之为静态成员函数

Note

注意:静态成员变量一定要在类外进行初始化

static成员的特点

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,在使用sizeof计算类大小时同样不会包括static成员的大小
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问(前提是非private属性)
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受publicprotectedprivate 访问限定符的限制
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class test
{
private:
    int _num;//非静态成员变量
    static int _count;//静态成员变量
public:
    test(int num)
        :_num(num)
    {}

    static int getCount()
    {
        _num = 1;//无法访问非静态成员变量
        return _count;
    }
};

int main()
{
    test t(1);

    return 0;
}
报错信息
非静态成员引用必须与特定对象相对

例如下面的题目:

求1+2+3+...+n

题目链接:求1+2+3+...+n_牛客题霸_牛客网 (nowcoder.com)

Quote

描述:

求1+2+3+...+n,要求不能使用乘除法、forwhileifelseswitchcase等关键字及条件判断语句(A?B:C)。

数据范围:0<n≤200 进阶: 空间复杂度O(1) ,时间复杂度 O(n)

参考代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Sum
{
private:
    static int _sum;
    static int _i;
public:
    Sum()
    {
        _sum+=_i;
        _i++;
    }
    static int getSum()
    {
        return _sum;
    }
};
int Sum::_i = 1;
int Sum::_sum = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Sum s[n];
        return Sum::getSum();
    }
};

优化为:

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Solution {
  public:
    class Sum {
      public:
        Sum() {
            _sum += _i;
            _i++;
        }
    };
    int Sum_Solution(int n) {
        Sum s[n];
        return _sum;
    }
  private:
    static int _sum;
    static int _i;
};
int Solution::_i = 1;
int Solution::_sum = 0;

友元

在C++中,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。

友元分为:友元函数和友元类

友元函数

在默认构造函数章节中,对流插入运算符和流提取运算符进行重载时,如果将这两个运算符重载函数放置在类中时,那么ostreamistream的对象将与this指针抢占第一个参数的位置,导致最后调用时和正常的输出输入参数相反,而解决这个问题时考虑到将这两个重载函数放置到全局,但是放置到全局时将无法看到类成员变量,此时为了同时解决前面两个问题,考虑使用友元函数,如下面代码

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test()
        :_num()
    {}

    //友元函数声明
    //友元函数声明可以放在任意位置
    friend ostream& operator<<(ostream& cout, const test& d);
    friend istream& operator>>(istream& cin, test& t);
};

//重载流插入运算符
ostream& operator<<(ostream& cout, const test& t)
{
    cout << t._num;
    return cout;
}

//重载流提取运算符
istream& operator>>(istream& cin, test& t)
{
    cin >> t._num;
    return cin;
}

int main()
{
    test t;
    cin >> t;
    cout << t;
    return 0;
}
输入
2
输出结果
2

友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend关键字

友元函数的特点:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用原理相同

友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

友元类的特点:

  1. 友元关系是单向的,不具有交换性
  2. 友元关系不能传递

    Note

    若C是B的友元类(即C想使用B类中的成员),B是A的友元类(即B想使用A类中的成员),不能推出C是A的友元类(即C依旧无法使用A中的成员)

  3. 友元关系不能继承(见继承章节

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class B
{
private:
    int _numB;
    friend class A;//声明A是B的友元类
public:
    B()
        :_numB(2)
    {}
};

class A
{
private:
    int _numA;
    B _b;
public:
    A()
        :_numA()
    {}

    void print()
    {
        cout << _b._numB << endl;
    }
};

int main()
{
    A a;
    a.print();
    return 0;
}
输出结果
2

在上面的代码中,定义了两个类A和B,在类B中声明了类A为B的友元类,则A类中可以访问B中的成员

Note

注意,尽管A类是B类的友元类,但是B类不可以访问A类中的成员,即单向传递

Tip

友元类的声明规则:当某个类想使用另一个类中的成员时,就在另一个类中声明友元类,例如在上面的代码中,A类想使用B类的成员,则A类在B类声明友元类(即想用哪一个类就在哪个类声明友元类)

内部类

如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。

Note

内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元,即外部类不可以访问内部类成员

内部类特点:

  1. 内部类可以定义在外部类的publicprotectedprivate都是可以的(若内部类具有private属性,则不能使用外部类::内部类 对象名 的方式创建对象)
  2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名
  3. sizeof(外部类)=外部类,和内部类没有任何关系
  4. 内部类虽然与外部类独立,但是内部类的生命周期还是外部类域
C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <iostream>
using namespace std;

class outer
{
private:
    int _num;
    static int _num1;

public:
    outer()
        :_num()
    {}
    class inner
    {
    private:
        int _num2;
    public:
        void print(const outer& o)
        {
            cout << o._num << endl;//非静态成员需要外部类对象
            cout << _num1 << endl;//静态成员可以不使用外部类对象
        }
    };
};

int outer::_num1 = 2;

int main()
{
    outer o;
    outer::inner i;
    i.print(o);
    return 0;
}
输出结果
0
2

在上面的代码中,外部类outer中有一个inner内部类,该内部类中可以通过外部类的对象o访问外部类的成员,而对于外部类的静态成员则可以直接访问

Note

注意,内部类中不可以创建外部类对象

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
using namespace std;

class outer
{
private:
    int _num;
    static int _num1;
public:
    outer()
        :_num()
    {}
    class inner
    {
    private:
        int _num2;
    public:
        void print(const outer& o)
        {
            cout << o._num << endl;//非静态成员需要外部类对象
            cout << _num1 << endl;//静态成员可以不使用外部类对象
        }
    };
};

int outer::_num1 = 2;

int main()
{
    outer o;
    outer::inner i;
    i.print(o);
    return 0;
}
报错信息
outer::inner::_o使用未定义的 classouter

因为内部类和外部类是两个单独的类,所以在使用sizeof计算外部类大小时不会包括内部类的大小

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class outer
{
private:
    int _num;
    static int _num1;
public:
    outer()
        :_num()
    {}
    class inner
    {
    private:
        int _num2;
    };
};

int outer::_num1 = 2;

int main()
{
    cout << sizeof(outer) << endl;
    return 0;
}
输出结果
4

匿名对象

所谓匿名对象,即类对象在实例化时不给名称

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test(int num)
        :_num(num)
    {}

    void print()
    {
        cout << _num << endl;
    }
};

int main()
{
    //普通对象
    test t(1);
    t.print();
    //匿名对象
    test(2).print();

    return 0;
}
输出结果
1
2

在上面的代码中,创建了两个对象,一个对象是普通对象,普通对象的生命周期为当前函数栈帧空间销毁之前,另外一个对象是匿名对象,匿名对象的生命周期为所在行,当匿名对象执行完所在行之后就会被销毁

Note

注意:匿名对象与常量一样,当给自定义类型的引用变量时需要有const

但是如果使用一个常量引用接受匿名对象时,匿名对象不会在当前行执行完毕后销毁

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test()
        :_num()
    {
        cout << "构造函数" << endl;
    }

    void print()
    {
        cout << _num << endl;
    }

    ~test()
    {
        cout << "析构函数" << endl;
    }
};

int main()
{
    //无常量引用的匿名对象
    test();
    //常量引用的匿名对象
    const test& t = test();

    test t1;
    return 0;
}
输出结果
构造函数
析构函数
构造函数
构造函数
析构函数
析构函数

在上面的代码中,对于无常量引用的匿名对象来说,当前行执行完毕后将会调用析构函数进行销毁,而常量引用的匿名对象则是在准备出当前作用域时销毁

拷贝对象时编译器做出的一些优化

Note

下面的优化结果由编译器决定,不同的编译器优化结果可能不同,视具体情况而定

参数传递优化

  1. 在前面的explicit关键字部分提到过编译器会对在单行的构造+拷贝构造优化为构造

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {}
    
        void print()
        {
            cout << _num << endl;
        }
    };
    
    int main()
    {
        test t = 1;
        t.print();
        return 0;
    }
    输出结果
    1
    
  2. 在给函数形参传递实参时,如果直接传递已经创建的对象时,编译器不会对其进行优化

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    void func1(const test t)
    {
        cout << "func1" << endl;
    }
    
    int main()
    {
        test t1(1);
        func1(t1);
        return 0;
    }
    输出结果
    构造函数
    拷贝构造函数
    func1
    析构函数
    析构函数
    

    在上面的代码中,首先test类创建了一个对象为t1,此时调用构造函数,当t1作为函数实参传递给func1函数,此时会调用拷贝构造函数将t1对象拷贝给形参t,接着进入func1函数栈帧空间执行func1函数体的语句,当func1函数结束执行后调用析构函数销毁形式参数对象t,最后调用析构函数销毁局部对象t1

    对上面的代码进行改进,直接传递整型1给func1函数,如下面代码

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    void func1(const test t)
    {
        cout << "func1" << endl;
    }
    
    int main()
    {
        func1(1);
        return 0;
    }
    输出结果
    构造函数
    func1
    析构函数
    

    在上面的代码中,直接将1作为对象传递给自定义类型的形参t时,常规的步骤为:调用构造函数用整型1初始化一个临时对象,再调用拷贝构造函数将临时对象中的内容拷贝给形参对象,但是此处编译器会对其进行优化为直接调用构造函数,用整型1初始化形参对象t

    同理,使用匿名对象作为实际参数传递给自定义类型的形参时,编译器也会有所优化

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    void func1(const test t)
    {
        cout << "func1" << endl;
    }
    
    int main()
    {
        func1(test(2));
    
        return 0;
    }
    输出结果
    构造函数
    func1
    析构函数
    

    在上面的代码中,使用整型2创建了一个匿名对象,常规步骤为:调用构造函数使用整型2创建匿名对象,接着调用拷贝构造函数将匿名对象中的内容拷贝给形式参数,但是编译器优化为直接使用整型2为形式参数初始化

    但是如果函数的形式参数为引用时,则不会有任何优化,直接调用构造函数进行初始化对象再由自定义类型的引用形参接收实参对象的地址

    Note

    注意:使用引用传参时一定要在形式参数处加const修饰

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    void func1(const test& t)
    {
        cout << "func1" << endl;
    }
    
    int main()
    {
        test t1(1);
        func1(t1);
        cout << endl;
        func1(1);
        cout << endl;
        func1(test(1));
    
        return 0;
    }
    输出结果
    构造函数
    func1
    
    构造函数
    func1
    析构函数
    
    构造函数
    func1
    析构函数
    析构函数
    

返回值优化

C++
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include <iostream>
using namespace std;

class test
{
private:
    int _num;
public:
    test(int num)
        :_num(num)
    {
        cout << "构造函数" << endl;
    }

    void print()
    {
        cout << _num << endl;
    }

    test(const test& t)
    {
        _num = t._num;
        cout << "拷贝构造函数" << endl;
    }

    test& operator=(const test& t)
    {
        cout << "赋值运算符重载函数" << endl;
        if (this != &t)
        {
            _num = t._num;
        }
        return *this;
    }

    ~test()
    {
        cout << "析构函数" << endl;
    }
};

test func()
{
    cout << "func" << endl;
    test t(1);
    return t;
}

int main()
{
    func();

    return 0;
}
  1. 当调用的函数有返回对象时,使用该对象初始化对象

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test(int num)
            :_num(num)
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        test& operator=(const test& t)
        {
            cout << "赋值运算符重载函数" << endl;
            if (this != &t)
            {
                _num = t._num;
            }
            return *this;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    test func()
    {
        cout << "func" << endl;
        test t(1);
        return t;
    }
    
    int main()
    {
        test t1 = func();
    
        return 0;
    }
    

    在上面的代码中,使用func函数的返回值初始化t1对象,常规的过程为:调用拷贝构造函数将func函数的返回值放入一个自定义类型的临时变量中,再通过拷贝构造函数将临时变量中的内容拷贝给t1对象,但是这里编译器会优化为调用一个构造函数将func的返回值作为初始化值直接初始化t1对象

    但是如果将两个步骤分开,如下面的代码

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test()
            :_num()
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        test& operator=(const test& t)
        {
            cout << "赋值运算符重载函数" << endl;
            if (this != &t)
            {
                _num = t._num;
            }
            return *this;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    test func()
    {
        cout << "func" << endl;
        test t;
        return t;
    }
    
    int main()
    {
        test t1;
        t1 = func();
        return 0;
    }
    

    在上面的代码中,因为t1对象需要完成实例化,所以会调用构造函数,接着执行t1 = func()语句,因为赋值运算符有从右往左的结合性,所以先执行func函数,在func函数中会再次调用构造函数创建一个对象,(注意中间有一个过程为:调用拷贝构造将返回对象拷贝到临时对象中,再调用析构函数销毁局部对象t)此时执行赋值语句,此时调用赋值运算符重载函数,将t对象的内容给t1对象

  2. 当返回的是匿名对象,使用该匿名对象初始化对象

    C++
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    #include <iostream>
    using namespace std;
    
    class test
    {
    private:
        int _num;
    public:
        test()
            :_num()
        {
            cout << "构造函数" << endl;
        }
    
        void print()
        {
            cout << _num << endl;
        }
    
        test(const test& t)
        {
            _num = t._num;
            cout << "拷贝构造函数" << endl;
        }
    
        test& operator=(const test& t)
        {
            cout << "赋值运算符重载函数" << endl;
            if (this != &t)
            {
                _num = t._num;
            }
            return *this;
        }
    
        ~test()
        {
            cout << "析构函数" << endl;
        }
    };
    
    test func()
    {
        cout << "func" << endl;
        return test();
    }
    
    int main()
    {
        test t1 = func();
        return 0;
    }
    输出结果
    func
    构造函数
    析构函数
    

    在上面的代码中,先执行func函数,常规步骤为:执行test类中的构造函数创建一个匿名对象,接着调用拷贝构造将匿名对象拷贝到临时对象中返回,接着调用拷贝构造将返回值拷贝给t1对象,但是此处编译器会优化为直接用返回的匿名对象的内容作为初始值初始化对象t1

总结:

  1. 为了编译器更好得优化,在传参数时,可以考虑使用引用变量作为参数
  2. 当使用到返回值时,如果能用引用就使用引用,不能使用引用需要返回值时,可以考虑返回匿名对象