class Test{
private:
int ma;
public:
// explicit Test(int a = 10):ma(a) { cout<<"Test(int)"<
看看上面这个类的定义,在main函数中,对象背后调用了什么方法?
int main()
{
Test t1; //构造函数
Test t2(t1);//拷贝构造函数
Test t3 = t2; //因为定义对象了,所以调用拷贝构造函数
Test t5 = Test(60);//原理等同于: t4
Test t4 = Test(20); //Test(20)就是显式生成临时对象,临时对象没有名字,
所以生存周期就是所在的语句,语句结束之后,临时对象就析构了;
//通常我们认为:调用构造函数创建临时对象,然后t4调用拷贝构造函数通过临时对象构造自己,
然后析构函数释放临时对象,但C++编译器在这里一般都有优化;
//注意:一般C++编译器都有这个优化:《用临时对象拷贝构造一个新对象,那么临时对象就不产生了,直接构造新对象》
//所以这里没有临时对象了,直接调用构造函数构造t4;跟Test t4(20)没有区别;
//类似于:string str1 = string("hello"); 等价于 string str1("hello");
t4 = t2 ;//调用拷贝赋值运算符:t4.operator = (const Test&) ;
t4 = Test(30); //这里t4已经产生了,不是定义;所以这里是调用构造函数生成临时对象,
//然后t4调用拷贝赋值运算符,临时对象被当成函数参数传进去,之后临时对象析构;
cout<<"-----------------------"<
再来看另外一个例子,体会对象背后调用了哪些方法!
class Test{
public:
Test(int a= 5,int b = 5):ma(a),mb(b){cout<<"Test(int,int)"<
看看下面代码背后调用了哪些函数:
class Test {
public:
Test(int data = 10) :ma(data) { cout << "Test(int)" << endl; }
~Test() { cout << "~Test()" << endl; }
Test(const Test& t) :ma(t.ma) { cout << "Test(const Test&)" << endl; }
void operator = (const Test& t) {
ma = t.ma;
cout << "Test::operator = " << endl;
}
int getData()const {
return ma;
}
private:
int ma;
};
Test GetObject(Test t) {
//不能返回local object局部对象的指针或者引用,因为函数结束后局部变量就析构了
//实参给形参是值传递,所以调用3:Test(const Test&)拷贝构造函数
int val = t.getData();
Test tmp(val); //4:Test(int)构造函数
return tmp; //5.Test(const Test&)拷贝构造
//6.~Test()析构函数析构t
//7~Test()析构函数析构tmp
//tmp是函数内局部对象,要想返回到main函数,
//会在main函数上开辟一块临时空间,然后调用拷贝构造函数拷贝tmp到main函数栈内存上
//<函数参数压栈原理和返回值传递原理:看程序员自我修养-装载链接与库>
}
int main() {
Test t1; //1:Test(int)构造函数
Test t2;//2:Test(int)构造函数
t2 = GetObject(t1); //8:t2调用拷贝赋值运算符,参数是临时对象
//9.析构函数:临时对象
//10.t2析构函数
//11.t1析构函数
return 0;
针对上面代码,发现编译器在背后调用了大量的函数,如何优化呢?3条原则
1.函数参数优先用引用传递:pass by reference (加const否看函数内部是否更改参数)
2.函数返回对象时候,应该优先return 临时对象,而不要返回一个定义过的对象;
3.接受《返回值是对象的函数》优先按初始化的方式接受,不要按赋值的方式接受;
通过3条原则优化上面代码如下:
Test GetObject(const Test &t) {
//函数参数按引用传递:减少了t的拷贝构造函数和析构函数调用
int val = t.getData();
return Test(val); //函数返回:直接return 临时对象
//不需要在这里构造临时对象了,直接构造mian函数栈上的临时对象
}
int main1() {
//优化:1.函数参数优先用引用传递:pass by reference (const看函数内部更改变量否选择加不加)
//2.函数返回对象时候,应该优先return 临时对象,而不要返回一个定义过的对象;
//3.接受<返回值是对象的函数>,优先按初始化的方式接受,不要按赋值的方式接受;
/*注:《用临时对象拷贝构造一个新对象,C++会进行优化,不产生临时对象了,直接用产生临时对象的方式去构造新对象;》 */
Test t1; //1:Test(int)构造函数
Test t2;//2:Test(int)构造函数
t2 = GetObject(t1);
//8:产生man函数中的临时对象《3.构造函数》,t2调用4.拷贝赋值运算符,参数是临时对象;
//5.析构函数:临时对象
//6.t2析构函数
//7.t1析构函数
return 0;
}
//mian函数中:接受返回值是临时对象的函数,优先用初始化的方式接受,而不是赋值的方式;
int main() {
Test t1; //1:Test(int)构造函数
Test t2 = GetObject(t1);//2构造函数
//用临时对象拷贝构造一个新对象,临时对象不产生了,直接用产生临时对象的方式去构造新对象
//2个析构函数
return 0;
}
首先了解一下一下右值和右值引用:
int a = 10;//a是左值:有内存,有名字 ; 10是右值:临时对象,没名字;
int& b = a; //左值引用绑定左值
const int& d = 10;
/*常左值引用可以绑定到右值上,实际是右值生成了一个临时对象,
d引用到哪个临时对象上了,此时临时对象的生命周期跟引用d绑定了;d只能读不能写*/
// int&& c = a; //错误;无法将左值绑定到右值引用上面;右值引用只能绑定右值;
int&& c = 10; /*c是右值引用类型,<它本身是左值>,它绑定到右值10上面;
实际上这里也是10生成临时变量,c绑定到临时变量上;但是c可读可写;
注:右值引用类型的变量本身是左值;
*/
给自定义String类添加带右值引用的拷贝构造函数和拷贝赋值运算符:
/*类定义中增加带右值引用的拷贝构造函数和拷贝赋值运算符;
那么当临时对象进行拷贝或者赋值时候就调用这两个版本了;*/
CMyString::CMyString(CMyString&& str)
//右值引用类型参数:临时对象调用的右值引用的拷贝构造函数
{
cout << "CMyString(CMyString&&)" << endl;
m_data = str.m_data;
str.m_data = nullptr;
}
CMyString& CMyString::operator=(CMyString&& str)
//右值引用类型参数:临时对象调用的右值引用的拷贝赋值运算符
{
cout << "operator = (CMyString&&)" << endl;
if (this == &str) {
return *this;
}
delete[] m_data;
m_data = str.m_data;
str.m_data = nullptr;
return *this;
}
对自定义vector中的push_back实现右值引用版本
template
void push_back(Ty&& val) {
/* Ty&& 就是万能引用: push_back可以接受左值也可以接受右值;
引用不是对象,通常不能定义引用的引用,但是通过模板类型参数就可以定义引用的引用;
1.<右值引用的特殊类型推断原则>:如果将一个左值传递给函数的右值引用参数,且此右值引用参数指向模板类型参数(如:Ty&&),编译器会推断模板类型参数为实参的左值引用类型;
2.<引用折叠>:如果通过模板类型参数创建了引用的引用,那么会发生引用折叠;即除了右值引用的右值引用会折叠为右值引用,其他情况引用折叠的结果都是左值引用; 参考C++Primer;
所以这里如果传递左值,类型推导Ty类型就是CMyString& ,那么函数形参CMySting& && 引用折叠为CMyString&;如果传递右值,类型推导Ty类型就是CMySting&&,那么函数形参CMyString&& &&引用折叠为CMyString&&;*/
if (full()) {
expand();
}
/*_allocator.construct(_last, val);
但是在construct这里不管val是左值引用类型还是右值引用类型,val变量它本身是左值;
所以这里匹配的是construct的左值引用版本,怎么解决呢?通过完美转发std::forward即可
_allocator.construct(_last, std::forward(val));
通过类型完美转发,如果val是左值引用类型那么返回左值,匹配左值引用参数的construct函数;
如果val是右值引用类型那么返回右值,匹配右值引用参数的construcct函数;
总结:std::forward:类型完美转发,能够识别左值和右值类型; */
std::move:移动语义,将左值强转为右值;
_last++;
}
template
void construct(T p, Ty&& val) {
//指针还是元素的T类型;Ty是给引用变量用的
new (p) T(std::forward(val));
}
补充其他知识点:
普通全局变量:在本文件中可以无限制使用,其他文件中通过extern关键字声明后也可以使用;
全局静态变量:即全局静态变量只能给本文件使用,其他文件不能使用;即在普通全局变量上取消了extern关键字声明,
局部静态变量:作用域在定义它的那个函数内,编译器在编译阶段为其分配地址,但是执行到它才进行初始化,且只初始化一次,编译器通过一个标志判断是否已经对局部静态变量进行初始化了;
三者主要区别就在于作用域不同,声明周期从程序运行开始到结束;