一、构造函数
不同的语法使用场景会自动调用功能不同的构造函数。c++的构造函数主要有如下几种类型:无参(多参)构造函数、拷贝构造函数、移动构造函数、赋值构造函数(赋值语句)、移动赋值构造函数(移动赋值语句)、类型转换构造函数;
- 无参构造函数:无参构造函数称为默认构造函数;如果未定义任何构造函数,编译器默认会生成一个
如下情况会调用无参构造函数(多个参数的构造函数也一样)
class MyClass
{
public:
MyClass():myInt_val(0),myString(""){}
MyClass(int val,string str):myInt_val(val),myString(str){}
private:
int myInt_val;
string myString;
};
MyClass myObj1;
MyClass myObj11(3,"str");
MyClass *myObj2 = new MyClass;
MyClass *myObj3 = new MyClass();
MyClass *myObj33 = new MyClass(3,"str");
MyClass *myObj4 = new MyClass[2];
- 拷贝构造函数/移动构造函数:=进行初始化、作为函数参数、函数返回值等等时调用
拷贝构造函数,参数为自身类型的引用类型或者常引用类型称为拷贝构造函数。如果未定义,编译器会默认生成一个拷贝构造函数,且为浅拷贝;
移动构造函数,参数为类的右值引用类型
class MyClass
{
public:
MyClass(const MyClass& obj):myInt_val(obj.myInt_val),
myString(string(obj.myString)){
cout<< "这里是拷贝构造函数" <<endl;
}
MyClass(MyClass&& obj):myInt_val(obj.myInt_val){
myString = std::move(obj.myString);
cout<< "这里是移动构造函数" <<endl;
}
private:
int myInt_val;
string myString;
static MyClass funcArg(MyClass obj,MyClass& obj2) {
MyClass obj3 = obj;
return obj3;
}
static MyClass& funcArg2(MyClass* obj) {
MyClass *retObj = new MyClass;
*retObj = *obj;
return *retObj;
}
};
MyClass myObj1;
// 拷贝构造函数
MyClass myObj5 = myObj1;
// 参数为类类型时:会调用拷贝构造函数。
// 参数或者返回值为类引用类型时:不调用构造函数
// 返回值为类类型时:添加-fno-elide-constructors,调用拷贝或者移动构造函数;不添加,编译器会优化,不调用任何构造函数
funcArg(myObj1, myObj1);
// 参数为指针、返回值引用或者指针:不调用任何构造函数
funcArg2(&myObj1);
- 赋值构造函数/移动赋值构造函数: 主要当使用=进行赋值时调用
赋值构造函数:即进行对象赋值时调用的构造函数。如果未定义,编译器会默认生成一个,简单进行遍历的赋值
1、参数必须为引用类型或者常引用类型
2、返回值必须是引用类型
3、必须return *this; 保证前后为一个对象
移动赋值构造函数,与赋值构造函数区别是参数为右值类型,其它一样
如果都未定义,编译器都会默认各生成一个
class MyClass
{
public:
MyClass& operator =(const MyClass & obj){
cout<< "这里是赋值构造函数" <<endl;
myInt_val = obj.myInt_val;
myString = obj.myString;
return *this;
}
MyClass& operator =(const MyClass && obj){
cout<< "这里是移动赋值构造函数" <<endl;
myInt_val = obj.myInt_val;
myString = std::move(obj.myString);
return *this;
}
private:
int myInt_val;
string myString;
};
MyClass myObj1;
// 当未定义移动构造函数或者移动赋值语句,调用赋值语句
// 否则调用移动赋值语句
myObj1 = global_func();
- 类型转换构造函数:类型转换构造函数,参数只有一个并且非自身类型,不常用
class MyClass
{
public:
MyClass(int val) {
myInt_val = val;
cout<< "这里类型转换构造函数" <<endl;
}
private:
int myInt_val;
string myString;
};
// 显示调用转换构造函数MyClass(int val)
MyClass myObj1(3);
- 个人思考:这么多构造函数,其实大部分情况下使用编译器默认的即可。对于频率调用较高的拷贝构造函数,当类中成员使用智能指针后,拷贝构造函数也只是指针的赋值,不存在效率问题,所以也无需定义移动构造函数了。就像oc的拷贝一样,大部分情况下也是这样。
二、智能指针
c++中有四个智能指针,auto_ptr,shared_ptr,weak_ptr,unique_ptr,其中后三个是c++11支持的,第一个已经被弃用,属于c++98的标准。
为什么要用智能指针?用来自动的实现对象的内存管理,不需要手写delete语句
实现原理:智能指针实际上是一个模板类,魔板类通过引用计数管理对应的指针内存,当类在离开作用域时会自动执行析构函数,这个模板类在析构函数中判断如果引用计数为0,则释放该指针对应的内存。
1、shared_ptr:多个智能指针可以指向同一个指针对象,每次引用计数都会+1
2、weak_ptr:仅仅指向指针对象,引用计数不变,用来解决shared_ptr导致的循环引用问题
3、unique_ptr: 每一个智能指针只能同时指向一个指针对象,也就是它的引用计数最多为1
使用例子:
class SmartTestClass {
public:
SmartTestClass(){
cout<<"这里是构造函数 SmartTestClass \n";
}
~SmartTestClass(){
cout<<"这里是析构函数 SmartTestClass \n";
}
};
class Smart_ptr_Test
{
public:
Smart_ptr_Test(string s)
{
str = s;
sharedClass = std::make_shared<SmartTestClass>();
cout << str<<" Test creat\n";
}
~Smart_ptr_Test()
{
cout<<"Test delete:"<<str<<" buffer "<<buffer<<endl;
}
string& getStr()
{
return str;
}
void setStr(string s)
{
str = s;
}
void print()
{
cout<<str<<" buffer "<<endl;
}
std::shared_ptr<SmartTestClass> getPtr(){
return sharedClass;
}
string buffer;
private:
string str;
std::shared_ptr<SmartTestClass> sharedClass;
};
unique_ptr<Smart_ptr_Test> fun()
{
return unique_ptr<Smart_ptr_Test>(new Smart_ptr_Test("unique_ptr_789"));
}
unique_ptr<Smart_ptr_Test> utest(new Smart_ptr_Test("unique_ptr 123"));
unique_ptr<Smart_ptr_Test> utest2 = make_unique<Smart_ptr_Test>("unique_ptr 456");
ptest->print();
if(ptest == NULL)cout<<"ptest = NULL\n";
Smart_ptr_Test* p = ptest2.release();
p->print();
ptest.reset(p);
ptest->print();
ptest2 = std::move(ptest);
//这里可以用 =,因为unique_ptr实现了移动构造函数
ptest2 = fun();
// 编译报错,因为unique_ptr同时智能指向一个指针对象
ptest2 = ptest;
- 个人思考:智能指针确实可以解放对于动态内存的释放操作,但是也会造成循环引用得问题。不过c++循环引用只有相互引用才会产生,这种情况应该比较少见,不管怎样,极力推荐
三、lamda表达式
即匿名函数,闭包表达式
- 语法参数:
[捕获列表 ] ( 参数 列表) [可选值] -> 返回值 { 函数体 }
其中捕获列表的含义如下:
[] 不捕获任何变量。
[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
[=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。
[bar] 按值捕获 bar 变量,同时不捕获其他变量。
[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。
可选值:默认没有,可以为mutable 代表可以在函数体内修改外部补货变量的值,和OC的__block类似
- 使用例子:
//全局变量
int all_num = 0;
/** lambda表达式的实现原理:在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。
*/
void lambda_main()
{
//局部变量
int a = 1;
int b = 2;
cout << "lambda1:\n";
auto lambda1 = [=] () mutable {
//全局变量可以访问甚至修改
all_num = 10;
a = 20;
//加入mutable后则可以对外部变量进行修改;否则编译器会报错
cout << a << " "
<< b << " "
<< endl;
};
lambda1();
cout << all_num << " " << num_1 <<endl;
cout << "lambda2:\n";
auto lambda2 = [&]{
all_num = 100;
a = 10;
b = 20;
cout << a << " "
<< b << " "
<< endl;
};
lambda2();
cout << all_num << endl;
}
- 原理:
在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。
假设lamda表达式捕捉了两个整数变量a和b、编译器的实现主要分为如下几个步骤:
1、创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象;
2、创建 lambda 对象
3、通过对象调用 operator()
伪代码如下:
class lambda_xxxx
{
private:
int a;
int b;
public:
lambda_xxxx(int _a, int _b) :a(_a), b(_b)
{
}
bool operator()(int x, int y) throw()
{
函数体......
}
};
void LambdaDemo()
{
int a = 1;
int b = 2;
auto lambda1 = [=] () {
......
return true
};
lambda1();
// 转化为如下:
int a = 1;
int b = 2;
lambda_xxxx lambda = lambda_xxxx(a, b);
bool ret = lambda.operator()(3, 4);
}
- 个人思考:lamada表达式会像oc/Swift那样造成循环引用吗?
不会,首先c++中造成循环引用得满足两个条件1、使用shared_ptr指针;2、相互引用;对于第2点,lamda表达式需要在捕获this的同时还得作为this所在类的成员变量,但是c++的静态特性决定了对于捕获了this的lamda表达式无法作为this所在类的成员变量,所以无法相互引用,即不会构成循环引用