🪐🪐🪐欢迎来到程序员餐厅💫💫💫
主厨:邪王真眼
主厨的主页:Chef‘s blog
所属专栏:c++大冒险
总有光环在陨落,总有新星在闪烁
新的类功能
默认成员函数:
-
原来C++类中有6个默认成员函数
- 构造函数
- 析构函数
- 拷贝构造函数
- 拷贝赋值重载
- 取地址重载
- const 取地址重载
-
C++11新增移动构造函数和移动赋值运算符重载。
默认移动构造和默认移动赋值的拷贝方式
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。
- 默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。
移动构造函数和移动赋值运算符重载生成条件
- 如果没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。
- 如果没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。
为什么设计这样的条件:
当涉及深拷贝时,我们 从我们之前实现的移动拷贝可以得知,对于深拷贝的移动构造,是所包含的资源转移要有程序员决定,
即:深拷贝的移动构造要由程序员决定,浅拷贝不用,所以通过上面的条件确定当前类是深拷贝还是浅拷贝
类成员变量初始化
class A
{int a = 0;//直接给缺省值,如果在构造函数没有处理a的值,那他就是我们所给的缺省值static int b;//静态成员变量不能给缺省值string s = "abc";//自定义类型也可以给缺省值
};
强制生成默认函数的关键字default:
class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p):_name(p._name),_age(p._age){}Person(Person&& p) = default;
private:bit::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
注意: 默认成员函数都可以用default关键字强制生成,包括移动构造和移动赋值。
禁止生成默认函数的关键字delete
class Person
{
public:Person(const char* name = "", int age = 0):_name(name), _age(age){}Person(const Person& p) = delete;
private:bit::string _name;int _age;
};
int main()
{Person s1;Person s2 = s1;Person s3 = std::move(s1);return 0;
}
final与override关键字
-
final修饰虚函数,表示该虚函数不能再被重写
class Person
{virtual int number()final{return 0;}
};
class Student :public Person
{virtual int number(){return 0;}
};
-
final修饰类,该类不能被继承
class Person final
{int a=0;
};
class Student :public Person
{};
-
override
如果派生类在虚函数声明时使用了override关键字,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译,如下:
派生类中由于fun1没有const修饰,导致没有实现虚函数重写,此时编译器会报错
class Base
{
public:virtual void fun1() const{;}};class Derived : public Base
{
public:virtual void fun1() override{;}};
可变参数模板
可变参函数模板定义:
// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}
可变参函数模板使用:
template<class ...Args>
void A(Args ...args)
{;
}
int main()
{A(0);A('a');A("aa", 'a', 0);
}
但是,我们如何解析参数包中的内容呢?
我们无法直接获取参数包args中的每个参数的, 只能通过展开参数包的方式来获取参数包中的每个参数。但语法不支持使用args[i]这样方式获取可变参数
template<class ...Args>
void ShowList(Args... args)
{for (int i = 0; i < sizeof...(args); i++){cout << args[i] << " ";}cout << endl;
}
递归函数方式展开参数包
展开函数
我们每调一次ShowList函数,就会把第一个参数T val打印,然后把args参数包传给下一次调用的ShowList,接着参数包里的第一个参数会被下一个函数的T类型接受,剩下的会被args接受,如此递归下去,每次剥离出参数包中的一个参数,直到参数包中的所有参数都被取出来。
template<class T,class ...Args>
void ShowList(T val, Args ...args)
{cout << val << endl;ShowList(args);
}
递归结束:
根据函数匹配原则我们设计一个函数模板,只有一个参数,这样当参数报只剩一个参数时就会跳出循环进入该函数,从而结束递归
template<class T>
void ShowList(T val)
{cout << val << endl;
}
逗号表达式展开参数包
template <class T>
void PrintArg(T t)
{cout << t << " ";
}
//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}
int main()
{ShowList(1);ShowList(1, 'A');ShowList(1, 'A', std::string("sort"));return 0;
}
可变参数模板的应用:emplace系列函数
emplace系列的接口,支持模板的可变参数,并且万能引用。
-
emplace与insert对比
int main()
{std::list< std::pair<int, char> > mylist;mylist.emplace_back(10, 'a');mylist.push_back({ 50, 'e' });return 0;
}
-
emplace系列接口相对insert的优势
emplace系列真正的优势在于浅拷贝的类
因为对于深拷贝的且实现了移动构造的类来说,移动构造代价很小,emplace的优势显现不出来。
浅拷贝效率低,则可以认为emplace_back节省了一次拷贝构造的时间,例如日期类
class Date
{
public://构造Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << "Date(int year, int month, int day)" << endl;}Date(Date &d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(Date&d)" << endl;}Date(Date&& d):_year(d._year), _month(d._month), _day(d._day){cout << "Date(Date&&d)" << endl;}
private:int _year = 1;int _month = 1;int _day = 1;
};
int main()
{list<Date> lt1;lt1.push_back({ 2024,3,30 });cout << "=============================================" << endl;lt1.emplace_back(2024, 3, 30);return 0;
}
总结
-
emplace接口使用需要直接传入参数包才能体现其价值,因为emplace真正高效的情况是传入参数包的时候,直接通过参数包构造出对象,避免了中途的一次拷贝。
-
emplace对于深拷贝的且实现了移动构造的类意义不大,因为移动构造的代价很小,emplace系列接口对于浅拷贝的类有可观的效率提升
-
使用emplace应该直接传参数包,因为如果传入的是对象(不管有名还是匿名),那么emplace系列接口的效率和insert接口的效率一样(都是调用拷贝构造)
🥰创作不易,你的支持对我最大的鼓励🥰
🪐~ 点赞收藏+关注 ~🪐