构造、析构与复制成员函数
● 构造函数:构造对象时调用的函数
class Str
{
public:
//构造函数名与类名相同,不需要返回值(目的是构造一个该类型的对象,默认的返回类型是该类类型)
Str() //缺省构造函数
{
std::cout << "Str() is called\n";
}
Str(int input) //参数列表不同构成重载,该构造函数用于初始化私有数据x
{
x = input;
std::cout << "Str(int input) is called\t" << x << std::endl;
}
private:
int x;
};
int main()
{
Str m;
std::cout << "end this line\n";
Str m2(3);
return 0;
}
– 名称与类名相同,无返回值,可以包含多个版本(重载)
– ( C++11 )代理构造函数
class Str
{
public:
/*Str() //#1
{
x = 3;
std::cout << "Str() is called\n";
}*/
Str() : Str(3) //原始构造函数
{
std::cout << "Str() : Str(3)\n";
}
//Str (int input = 3) //与#1构造函数内部逻辑相同,无代理构造函数时直接注释掉#1构造函数
Str(int input) //代理构造函数,since C++11
{
x = input;
std::cout << "Str(int input) is called\t" << x << std::endl;
}
private:
int x;
};
int main()
{
Str m(30);
std::cout << "End this line\n";
Str m2; //C++标准: 执行代理构造函数时,先执行代理函数的逻辑,在执行原始构造函数的逻辑
return 0;
}
● 初始化列表:区分数据成员的初始化与赋值
– 通常情况下可以提升系统性能
class Str
{
public:
Str(const std::string& val)
{
std::cout << "Pre-assignment: " << x << std::endl; //字符串的缺省初始化会将字符串初始化成空字符串
x = val; //核心的写操作, 是类类型的赋值
std::cout << "Post-assignment: " << x << std::endl;
}
private:
std::string x;
};
int main()
{
Str m("abc"); //#1, 该行代码相当于#2、#3行代码的效果,性能不佳
std::string x; //#2
x = "abc"; //#3
int var = 3; //初始化: 为变量申请内存空间并将该内存空间的值修改为等号后面的值
var = 5; //赋值: 只是将该内存空间的值修改为等号后面的值
return 0;
}
class Str
{
public:
Str(const std::string& val) : x(val), y(0) //x(val), y(0)是初始化列表,使用val初始化私有数据x,使用0初始化y
{
std::cout << "Pre-assignment: " << x << '\t' << y << std::endl; //字符串的缺省初始化会将字符串初始化成空字符串
std::cout << "Post-assignment: " << x << '\t' << y << std::endl;
}
private:
std::string x;
int y;
};
int main()
{
Str m("abc"); //使用列表初始化, 该行代码相当于#2行代码的效果,性能提升
std::string x = "abc"; //#2
return 0;
}
– 一些情况下必须使用初始化列表(如类中包含引用成员)
class Str
{
public:
Str(const std::string& val, int& _ref) //注意第二参数类型必须是int&: 如果是int _ref,ref会绑定到形参_ref(临时对象),构造函数结束后形参销毁,形成dangling reference
: x(val)
, y(0)
, ref(_ref)
{
ref = 3;
}
private:
std::string x;
int y;
int &ref;
};
int main()
{
int val = 0;; //#1
std::cout << val <<std::endl;
Str m("abc", val); //OK,类Str的私有数据引用ref绑定到#1的val
std::cout << val <<std::endl;
return 0;
}
– 注意元素的初始化顺序与其声明顺序相关,与初始化列表中的顺序无关
class Str
{
public:
Str(const std::string& val) : x(val) , y(x.size()) //OK,先后初始化x, y(C++标准)
//Str(const std::string& val) : y(x.size()), x(val) //Warning even Error: Field 'y' will be initialized after field 'x'
{
std::cout << x << '\t' << y <<std::endl;
}
private:
std::string x; //先声明x
size_t y; //后声明y
};
int main()
{
Str m("abc");
return 0;
}
笔记:在大量的应用背景下,C++抽象成用栈去存储自动存储持续变量,因此对象(或者数据成员)的构造顺序与销毁顺序恰好相反。如果对象数据成员的销毁顺序与初始化列表顺序相反,当类中有大量的数据成员用不同的初始化顺序来初始化时,编译器要记录每一个类对象的数据成员初始化顺序,加重了编译器负担,与C++强调性能背道而驰。所以无论初始化列表顺序是什么,初始化顺序只与声明顺序一致。
class Str
{
public:
Str(size_t input) : x(y+1) , y(input) //声明私有数据是顺序: 先声明x后声明y。用y值初始化x值时会出现意料之外的值
{
std::cout << x << '\t' << y <<std::endl;
}
private:
size_t x; //先声明x
size_t y; //后声明y
};
int main()
{
Str m(4);
return 0;
}
– 使用初始化列表覆盖类内成员初始化的行为
class Str
{
public:
Str()
{
std::cout << x << '\t' << y <<std::endl;
}
private:
size_t x = 3; //类内成员初始化
size_t y = 4; //类内成员初始化
};
int main()
{
Str m;
return 0;
}
class Str
{
public:
Str() : x(10), y(11) //初始化列表覆盖类内成员初始化,输出10 11
{
std::cout << x << '\t' << y <<std::endl;
}
private:
size_t x = 3; //类内成员初始化
size_t y = 4; //类内成员初始化
};
int main()
{
Str m;
return 0;
}
● 缺省构造函数:不需要提供实际参数就可以调用的构造函数
class Str
{
public:
Str() //缺省构造函数,无参数就可以调用
{
std::cout << x << '\t' << y <<std::endl;
}
private:
size_t x = 3;
size_t y = 4;
};
int main()
{
Str m;
return 0;
}
– 如果类中没有提供任何构造函数,那么在条件允许的情况下,编译器会合成一个缺省构造函数
struct Str
{
size_t x;
size_t y;
std::string val;
int& ref; //编译器无法(拒绝)对引用缺省初始化
};
– 合成的缺省构造函数会使用缺省初始化来初始化其数据成员
struct Str
{
size_t x;
size_t y;
std::string val;
};
int main()
{
Str m; // OK,编译器会自动合成缺省构造函数,与C兼容
std::cout << m.x << ' ' << m.y << ' ' << m.val <<std::endl;
return 0;
}
– 调用缺省构造函数时避免 most vexing parse
struct Str
{
size_t x;
size_t y;
std::string val;
};
int main()
{
Str m(); //Error: most vexing parse,此处是声明了一个无参的函数
Str m2{}; //OK
return 0;
}
– 使用 default 关键字定义缺省构造函数
struct Str
{
Str() = default; //OK, since C++11,与编译器合成的缺省构造函数的逻辑构造对象
Str(const std::string& input)
: val(input) {}
std::string val;
};
int main()
{
Str m{};
std::cout << m.val << std::endl;
return 0;
}
参考
深蓝学院:C++基础与深度解析