auto
可以使用auto来推导变量的实际类型,auto使用的前提是:必须要对auto声明的类型进行初始化,否则编译器无法推导出auto的实际类型。
typeid
只能查看类型不能用其结果类定义类型。
int a = 10;
cout<<typeid(a).name()<<endl;
decltype
decltype是根据表达式的实际类型推演出定义变量时所用的类型。比如:
1、推演表达式类型作为变量的定义类型
int a = 10; int b = 20;
decltype(a+b) c;// 用decltype推演a+b的实际类型,作为定义c的类型
2、推演函数返回值的类型
int add(int x1, int x2)
{
return x1 + x2;
}
int main()
{
//如果没有带参数,推导函数的类型
cout << typeid(decltype(add)).name() << endl;
//如果带参数列表,推导的是函数返回值的类型,注意:此处只是推演,不会执行函数
cout << typeid(decltype(add(10, 20))).name() << endl;
system("pause");
return 0;
}
default:可以让编译器显示生成某个默认成员函数。
delete:可以让编译器禁止生成某个默认成员函数。
A() = default;//让编译器显示生成默认构造函数
A(const A&) = delete;// 禁止编译器生成默认的拷贝构造函数以及赋值运算符重载
左值:可以被修改的对象,或可以取地址的对象
如:const int a = 10; a 不能被修改但是能取地址,所以 a 是一个左值
右值:
①纯右值:常量,基本类型表达式的返回值(如:100,a+b)
②将亡值:函数按照值的方式进行返回的临时自定义对象、表达式返回的临时自定义对象。(如:string s1,s2; s3 = s1+s2;当中的s1+s2就会返回一个临时自定义对象拷贝构造s3)
左值引用:左值引用就是给左值取别名。左值引用只可以引用左值,但是 const 左值引用既可以引用左值也可以引用右值。
int a = 10;
const int& a = 10;//const 引用右值
int b = 20;
const int& c = b;//const 引用左值
右值引用:右值引用就是给右值取别名。右值引用只可以引用右值,但是可以使用move 将左值的属性改为右值进行右值引用。
int b = 100;
int&& a = 10;//右值引用引用右值
int&& c = move(b);//右值引用通过move()引用左值
C++11提出了移动语义概念,即:将一个对象中资源移动到另一个对象中的方式。移动语义主要是右值引用带来的移动构造和移动赋值。移动构造与移动赋值也属于默认成员函数,在C++11中,编译器会为类默认生成一个移动构造、移动赋值,该移动构造、移动构造为浅拷贝,因此当类中涉及到资源管理时,用户必须显式定义自己的移动构造、移动赋值。对于不涉及资源管理的类来说,移动构造、移动赋值并起不了什么作用。
在之前写的 MyString 类的时候,我们重载了operator+()这个函数:
class String//其他函数已舍去
{
public:
.......
String(const String& s)
: _str(new char[strlen(s._str) + 1])
{
strcpy(_str, s._str);
}
String operator+(const String& s)
{
char* pTemp = new char[strlen(_str) + strlen(s._str) + 1];
strcpy(pTemp, _str);
strcpy(pTemp + strlen(_str), s._str);
String strRet(pTemp);
return strRet;
}
........
private:
char* _str;
};
int main()
{
String s1("hello");
String s2("world");
String s3 = s1 + s2;
return 0;
}
在我们执行 s3 = s1 + s2 这句代码时候,首先会调用 operator+()这个函数,由于strRet是个局部变量,出了作用域就销毁了,所以返回值是 string 而不是 string& 。而此时真正返回的值是一个strRet 通过拷贝构造出的一个临时对象,临时对象构造完以后 strRet 就被销毁 了。这个临时对象是一个右值(右值中的将亡值)。然后通过 s3 = 临时对象 调用拷贝构造函数,这个临时对象可以传参给拷贝构造函数(因为const 左值引用可以引用右值)。完成这些后进行拷贝构造,拷贝构造完成后这个临时对象就会被析构。仔细观察会发现:strRet、临时对象、s3每个对象创建后,都有自己独立的空间,而空间中存放内容也都相同,相当于创建了三个内容完 全相同的对象,对于空间是一种浪费,这样做还增加了拷贝的次数。如何解决这个问题呢?–》使临时对象被充分的利用起来?****移动构造和移动赋值可以解决这个问题:
String(String&& s)
: _str(nullptr)
{
swap(_str, s._str);
}
int main()
{
String s1("hello");
String s2("world");
String s3 = s1 + s2;//这里是移动构造
return 0;
}
上面所提到的那个strRet 通过拷贝构造出的一个临时对象(右值),然后 s3 = 临时对象 调用拷贝构造函数。临时对象是右值,因此在用临 时对象构造s3时,采用移动构造,将临时对象中资源转移到s3中,这样做减少了拷贝次数,大大提高程序运行的效率。
当然还有移动赋值,原理同上面相同。
String& operator=(String&& s)
{
_str = nullptr;
swap(_str, s._str);
return *this;
}
int main()
{
String s1("hello");
String s2("world");
String s3;
s3 = s1 + s2;//这里用的是移动赋值
return 0;
}
右值引用的应用主要在函数参数(移动构造、移动赋值)与函数的返回值(临时对象),都在上面那个例子体现。应用在函数参数还有一些其他场景,容器的插入:
移动构造带来的副作用:move将s1转化为右值后,在实现s2的拷贝时就会使用移动 构造,此时s1的资源就被转移到s2中,s1就成为了无效的字符串(s1会被置空),要避免这种情况的产生:
int main()
{
string s1("hello");
string s2(move(s1));
return 0;
}
函数模板在向其他函数传递自身形参时,如果相应实参是左值,它就应该被转发为左值;如果相 应实参是右值,它就应该被转发为右值。但是,模板传参过程中,实参的右值属性就会被丢失,都会被处理成左值。如何解决这个问题?
完美转发可以解决,C++11通过forward函数来实现完美转发:
①:实现移动语义(移动构造与移动赋值)
②:给中间临时变量取别名。
string s1("hello");
string s2(" world");
stirng&& s4 = s1 + s2//s4就是s1和s2拼接完成之后结果的别名
③:实现完美转发 (通过 forward 实现)
在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法,如果待排序元素为自定义类型,需要用户定义排序时的比较规则。随着C++语法的发展,开始觉得上面的写法太复杂了,每次为了实现一个 algorithm 算法, 都要重新去 写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了 极大的不便。因此,在C11语法中出现了lambda表达式。
struct Goods
{
string _name;
double _price;
};
int main()
{
Goods gds[] = { { "苹果", 2.1 }, { "香蕉", 3 }, { "橙子", 2.2 }, { "菠萝", 1.5 } };
//第一种用法
auto ret = [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; };
sort(gds, gds + sizeof(gds) / sizeof(gds[0]), ret);
//第二种写法,不用获取返回值直接用
sort(gds, gds + sizeof(gds) / sizeof(gds[0]), [](const Goods& g1, const Goods& g2)->bool{return g1._price < g2._price; });
for (int i = 0; i < 4; i++)
{
cout << gds[i]._name << ":" << gds[i]._price << endl;
}
system("pause");
return 0;
}
实际在底层编译器对于lambda表达式的处理方式,完全和仿函数一样。即:如果定义了一 个 lambda 表达式,编译器会自动生成一个名字叫做lambda_uuid ( uuid是一个字符串生成技术,它可以使每个 lambda 表达式都生成一个名字唯一的类),在该类中重载了operator(),lambda 表达式自身或用 auto 接受的返回值其实是一个对象,可以像仿函数那样使用。
struct add
{
int operator()(const int& a, const int& b)
{
return a + b;
}
};
int main()
{
//仿函数用法
add tmp1;
tmp1(10, 20);
//lambda表达式用法
auto tmp2 = [](const int& a, const int& b)->int{return a + b; };
tmp2(10, 20);
return 0;
}
可以看出 lambda 表达式用法与仿函数的用法并无差别。