文章目录
一、默认成员函数的控制
1.显式缺省函数
- 在C++11中,可以在默认函数定义或者声明时加上=default,从而显式的知指示编译器生成该函数的默认版本,用=default修饰的函数称为显式缺省函数。
class A
{
public:
A(int a)//有了该构造函数,编译器不会自动生成默认的构造函数
:_a(a)
{}
//显示缺省构造函数,由编译器生成
A() = default;//加了default此句代码后,即使我们写了构造函数,编译器也会自动生成默认的构造函数
//在类中声明,在类外定义时让编译器生成默认赋值运算符重载
A& operator=(const A& a);//加了default此句代码后,编译器会自动生成默认的赋值运算符重载
private:
int _a;
};
A& A::operator=(const A& a) = default;//在类外定义
int main()
{
A a(30);
A b;
b = a;
return 0;
}
2.删除默认函数
- 在C++98中,如果想要限制某些默认函数的生成,就将该函数权限设置为private,且不定义,这样只要类外想调用就会报错。在C++11中,如果想要限制某些默认函数的生成,只需在该函数声明加上=delete即可,该语法使编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。
class A
{
public:
A(int a)
:_a(a)
{}
//禁止编译器生成默认的拷贝构造函数
A(const A& a) = delete;
//禁止编译器生成默认的赋值运算符重载
A& operator=(const A& a) = delete;
private:
int _a;
};
int main()
{
A a(30);
//编译器报错,因为该类的拷贝构造函数已经删除
A b(a);
//编译器报错,因为该类的赋值运算符重载函数已删除
b = a;
return 0;
}
二、final和override关键字
1.final关键字
- fina加到某个类或某个虚函数之后,l可以修饰某个类或某个虚函数,被修饰的类就不可以被继承,被修饰的虚函数就不可以被子类重写。
#include <iostream>
class A final
{
public:
A(int a)
:_a(a)
{}
private:
int _a = 0;
};
class B
{
public:
virtual void Print() final
{
std::cout << "B类" << std::endl;
}
};
class C : public A
{};
class D : public B
{
public:
void Print()
{
std::cout << "D类" << std::endl;
}
};
2.override关键字
- override加在某个派生类的虚函数之后,如果派生类对该虚函数没有进行重写,则报错。用来检查派生类是否重写了基类的某个虚函数。
#include <iostream>
class A
{
public:
A(int a)
:_a(a)
{}
virtual void Print()
{
std::cout << "A类" << std::endl;
}
private:
int _a = 0;
};
class B : public A
{
public:
//正确
void Print() override
{
std::cout << "B类" << std::endl;
}
//不报错
void print()
{
std::cout << "B类" << std::endl;
}
//报错
void print()override
{
std::cout << "B类" << std::endl;
}
};
- 如上述例子可以得出,如过子类要对父类的虚函数进行重写,但是不小心在子类中写错函数名字,就达不到想要的目的,此时在要重写的虚函数之后加上override关键字,如果写错函数名字就会报错。
三、变量的类型推导
- C++98中已经支持RTTI(运行时类型识别,Run-Time Type Identification):
① typeid只能看类型,不能用其结果来定义类型。typeid(v),name()查看对象v类型。
#include <iostream>
using namespace std;
int main()
{
int c = 20;
cout << typeid(c).name() << endl;
return 0;
}
② dynamic_cast只能用于含有虚函数的继承体系中。dynamic_cast< type-id > (expression) 该运算符把expression转换成type-id类型的对象。将一个基类对象指针(或引用)cast到派生类指针。dynamic_cast会根据基类指针是否真正指向派生类指针来做相应处理,即会作出一定的判断。:若对指针进行dynamic_cast,失败返回null,成功返回正常cast后的对象指针;若对引用进行dynamic_cast,失败抛异常,成功返回正常cast后的对象引用。
#include <iostream>
using namespace std;
class A
{
public:
virtual void fun()
{
cout << "A::fun()" << endl;
}
};
class B : public A
{
public:
void fun()
{
cout << "B::fun()" << endl;
}
};
int main()
{
B b;
A* pa;
pa = dynamic_cast<A*>(&b);
pa->fun();
return 0;
}
1.auto
- auto是C++11中的类型推导,通过初始化来推导变量类型。auto e = _____通过初始化去推导,给你初始化什么,e类型就是什么。
#include <iostream>
using namespace std;
int main()
{
int a = 0;
auto b = a;
cout << "b的类型为:" << typeid(b).name() << endl;
char c = 'a';
auto d = c;
cout << "d的类型为:" << typeid(d).name() << endl;
double e = 8.99;
auto f = e;
cout << "f的类型为:" << typeid(f).name() << endl;
int* p = &a;
auto q = p;
cout << "q的类型为:" << typeid(q).name() << endl;
return 0;
}
2.decltype
- C++11中用于推导表达式的结果的类型。
#include <iostream>
using namespace std;
int main()
{
int a = 0, b = 3;
decltype (a + b)c;
cout << "c的类型为:" << typeid(c).name() << endl;
double e = 8.99, g = 2.89;
decltype (e - g)h;
cout << "h的类型为:" << typeid(h).name() << endl;
return 0;
}
四、范围for
- 范围for对于容器而言,底层是迭代器支持的。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> vc = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
for (auto e : vc)
cout << e << " ";
cout << endl;
return 0;
}
- 当e是拷贝代价大(深拷贝)的对象时,注意用&,如string、vector< string >等。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = "abcdefghijklmn";
for (auto& e : str)
cout << e << " ";
cout << endl;
return 0;
}
五、初始化列表
1.基本使用
#include <iostream>
#include <vector>
#include <set>
#include <map>
using namespace std;
int main()
{
vector<int> vc1 = { 1, 2, 3, 4, 5, 6, 7 };
vector<int> vc2{ 1,2,3,4,5,6,7 };//'='可以加可以不加
int* p1 = new int(10);//C++98
//int* p2 = new int[5](1);//C++98不支持new数组的初始化
int* p3 = new int[7]{ 1,2,3,4,5,6,7 };
set<int> s1 = { 7,5,4,6,1,2,3 };
set<int> s2{ 7,5,4,6,1,2,3 };//'='可以加可以不加
map<string, int> mp1 = { {"苹果", 5}, {"香蕉", 7}, {"橘子", 3}, {"西瓜", 3} };
map<string, int> mp2{ { "苹果", 5 },{ "香蕉", 7 },{ "橘子", 3 },{ "西瓜", 3 } };//'='可以加可以不加
return 0;
}
2.自定义类型
- C++98不支持多对象的列表初始化
#include <iostream>
using namespace std;
//C++98
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a = 0;
};
int main()
{
//调用构造函数
A a1(9);
//类型转换,先构造一个_a的值为2的临时变量对象
//再用该临时变量对象拷贝构造a2
//编译器会优化成该语句就是直接构造
A a2 = 2;
return 0;
}
- C++11支持多对象的列表初始化
#include <iostream>
using namespace std;
//C++98
class Point
{
public:
Point(int x = 0, int y = 0)
:_x(x)
,_y(y)
{}
void Print()
{
cout << _x << ", " << _y << endl;
}
private:
int _x = 0;
int _y = 0;
};
int main()
{
Point p1(1, 1);
Point p2 = (2, 6);
p1.Print();
p2.Print();
return 0;
}
- 上述例子中,_x = 2,_y = 0,而不是_x = 2,_y = 6。因为编译器将(2, 6)当作了逗号表达式。返回值是6,所以传递的是p2 = 6。
3.支持初始化列表的原理
#include <iostream>
using namespace std;
int main()
{
initializer_list<int> l = { 1,2,3,4,5 };
initializer_list<int>::iterator it = l.begin();
while (it != l.end())
{
cout << *it << " ";
++it;
}
cout << endl;
return 0;
}
- vector< int > vc = {1, 2, 3, 4, 5};
- C++11中,用vector initializer_list< value_type > il, const allocator_type& alloc = allocator_type(1);进行初始化,其他容器也一样。
六、右值引用
1.左值引用和右值引用的概述
- 左值引用和右值引用都是引用,即都是给对象取别名。
- 可以放在‘’=‘’左边的,或者能够取地址的称为左值;只能放在‘’=‘’右边的称为右值的,或不能取地址的称为右值。
#include <iostream>
using namespace std;
int main()
{
int a = 10;//a是一个左值,10是一个右值
//在左边一定是左值,而在右边不一定是右值
int b = a;//这里的a和b都是左值
const int c = 10;//这里的c不能算是左值
//左值通常是可以被修改的对象;右值通常是常量、表达式的返回值(临时对象)
int d = a + b;//a+b返回的一个临时对象就是右值
return 0;
}
2.左值引用&
#include <iostream>
using namespace std;
int main()
{
//1.左值引用可以引用左值
int a = 10;
int& b = a;
//2.左值引用不能引用右值,但const左值引用可以引用右值
const int& c = 10;
const int& d = a + c;
return 0;
}
3.左值引用的应用
① 做参数,如 swap(T& a, T& b)为输出型参数。func(const vector< int >& v)做函数参数,提高了传递效率。
② 做返回值,如T& operator[] (size_t index)改变返回对象。 vector< T >& operator=(const vector< T >& v)提高了效率。
4.右值引用&&
#include <iostream>
using namespace std;
int main()
{
//1.右值引用可以引用右值
int a = 10, b = 10;
int&& c = a + b;
int&& d = 10;
int&& e = a + 10;
//2.右值引用不能引用左值,但可以用move将左值转换成右值
int&& f = move(a);
return 0;
}
5.右值引用的应用
- 右值分为将亡值(函数或者表达式返回的临时自定义类型对象)和纯右值(如10、100、20、a+b等,常量和基本类型的临时变量)。
① 移动构造
- 有了移动构造,这里因为s1+s2的返回值是右值(该临时对象就是将亡值即右值),那么就会自动匹配调用移动构造。
- 本质:移动构造比拷贝构造效率更高,因为不涉及拷贝。
② 移动赋值
③ 移动构造、移动赋值和拷贝构造、拷贝赋值
- 移动构造/移动赋值和拷贝构造/拷贝赋值对于普通类都一样,但是对于管理资源需要深浅拷贝的类、意义重大。如string、vector、list、map、set、unordered_map、unordered_set。
- 在C++98中只能使用深拷贝(拷贝构造、拷贝赋值),而早C++11中可以使用移动构造、移动赋值。
④ 右值引用的应用:emplace_back()和emplace。
6.完美转发(模板)
① 模板既可以匹配左值也可以匹配右值
#include <iostream>
using namespace std;
void fun(int& x)
{
cout << "左值引用" << endl;
}
void fun(int&& x)
{
cout << "右值引用" << endl;
}
void fun(const int& x)
{
cout << "const左值引用" << endl;
}
void fun(const int&& x)
{
cout << "const右值引用" << endl;
}
template<class T>
void perfectForward(T&& t)
{
fun(t);
}
int main()
{
perfectForward(10);//右值引用
int a = 10;
perfectForward(a);//左值引用
perfectForward(move(a));//右值引用
const int b = 10;
perfectForward(b);//const左值引用
perfectForward(move(b));//const右值引用
return 0;
}
② 使用完美转发之后
#include <iostream>
using namespace std;
void fun(int& x)
{
cout << "左值引用" << endl;
}
void fun(int&& x)
{
cout << "右值引用" << endl;
}
void fun(const int& x)
{
cout << "const左值引用" << endl;
}
void fun(const int&& x)
{
cout << "const右值引用" << endl;
}
template<class T>
void perfectForward(T&& t)
{
fun(std::forward<T>(t));
}
int main()
{
perfectForward(10);//右值引用
int a = 10;
perfectForward(a);//左值引用
perfectForward(move(a));//右值引用
const int b = 10;
perfectForward(b);//const左值引用
perfectForward(move(b));//const右值引用
return 0;
}
七、lambda表达式
1.函数指针、仿函数和lambda表达式
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
struct fruits
{
string _name;
double _price;
int _weight;
};
//1.函数指针
bool PriceCompare(const fruits& f1, const fruits& f2)
{
return f1._price < f2._price;
}
bool WeightCompare(const fruits& f1, const fruits& f2)
{
return f1._weight < f2._weight;
}
//2.仿函数
struct PriceCompareObj
{
bool operator()(const fruits& f1, const fruits& f2)
{
return f1._price < f2._price;
}
};
struct WeightCompareObj
{
bool operator()(const fruits& f1, const fruits& f2)
{
return f1._weight < f2._weight;
}
};
int main()
{
fruits f[] = { {"苹果", 2.22, 6}, {"西瓜", 3.43, 10}, {"橘子", 1.5, 4} };
//1.函数指针
PriceCompare(f[0], f[1]);
sort(f, f + sizeof(f) / sizeof(f[0]), PriceCompare);
sort(f, f + sizeof(f) / sizeof(f[0]), WeightCompare);
//2.仿函数/函数对象
/*
比起函数指针的优势,即可作为类型在模板参数传递,也可以在函数参数作为对象
*/
PriceCompareObj pcoj;
pcoj(f[0], f[1]);
sort(f, f + sizeof(f) / sizeof(f[0]), pcoj);
WeightCompareObj wcoj;
wcoj(f[0], f[1]);
sort(f, f + sizeof(f) / sizeof(f[0]), wcoj);
//3.lambda表达式
/*
相比之前两种方式更加简单方便
*/
auto priceless = [](const fruits& f1, const fruits& f2)->bool
{
return f1._price < f2._price;
};
sort(f, f + sizeof(f) / sizeof(f[0]), priceless);
//->bool也可以省略
auto weightless = [](const fruits& f1, const fruits& f2)
{
return f1._weight < f2._weight;
};
sort(f, f + sizeof(f) / sizeof(f[0]), weightless);
sort(f, f + sizeof(f) / sizeof(f[0]), [](const fruits& f1, const fruits& f2)
{
return f1._price < f2._price;
});
sort(f, f + sizeof(f) / sizeof(f[0]), [](const fruits& f1, const fruits& f2)->bool
{
return f1._weight < f2._weight;
});
return 0;
}
2.lambda表达式书写
- [capture-list] ([arameters) mutable -> return-type {statement};
- ① [capture-list]:捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda函数使用。
②(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连()一起省略。
③mutable:默认情况下,lambda函数是一个const函数,mutable可以取消其常量性。使用该修饰符时,参数列表不可以省略(即使参数为空,也不可以省略参数列表)。
④->return-type:返回值类型。返回值类型明确的情况下,也可以省略,由编译器对返回值类型进行推导。
⑤{statement}:函数体。