本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。
原始字面量
在 Cpp11 中添加了定义原始字符串的字面量,定义方式为:R “xxx(原始字符串)xxx” 其中()两边的字符串可以省略。原始字面量 R 可以直接表示字符串的实际含义,而不需要额外对字符串做转译或连接等操作。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string str = R"test_baidu_html(
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta content="always" name="referrer">
<meta name="theme-color" content="#2932e1">
<link rel="shortcut icon" href="/favicon.ico" type="image/x-icon" />
<link rel="icon" sizes="any" mask href="//www.baidu.com/img/baidu_85beaf5496f291521eb75ba38eacbd87.svg">
<link rel="search" type="application/opensearchdescription+xml" href="/content-search.xml" title="百度搜索" />
<title>blibli哔哩哔哩官网_百度搜索</title>
)test_baidu_html";
cout << str << endl;
return 0;
}
指针空值类型 - nullptr
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
Cpp 中,void * 类型无法隐式转换为其他类型的指针,此时使用 0 代替 ((void *)0),用于解决空指针的问题。这个 0(0x0000 0000)表示的就是虚拟地址空间中的 0 地址,这块地址是只读的。
Cpp 中将 NULL 定义为字面常量 0,并不能保证在所有场景下都能很好的工作,比如,函数重载时,NULL 和 0 无法区分。
int* ptr1 = nullptr;
char* ptr2 = nullptr;
double* ptr3 = nullptr;
面的代码编译器会分别将 nullptr 隐式转换成 int*、char* 以及 double* 指针类型。
constexpr
const 关键字,从功能上来说这个关键字有双重语义:变量只读,修饰常量。
constexpr,这个关键字是用来修饰常量表达式的。所谓常量表达式,指的就是由多个(≥1)常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。
Cpp 程序从编写完毕到执行分为四个阶段:预处理、 编译、汇编和链接 4 个阶段,得到可执行程序之后就可以运行了。需要额外强调的是,常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算往往发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。
凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。在定义常量时,const 和 constexpr 是等价的,都可以在程序的编译阶段计算出结果,例如:
const int m = f(); // 不是常量表达式,m的值只有在运行时才会获取。
const int i=520; // 是一个常量表达式
const int j=i+1; // 是一个常量表达式
constexpr int i=520; // 是一个常量表达式
constexpr int j=i+1; // 是一个常量表达式
对于 Cpp 内置类型的数据,可以直接用 constexpr 修饰,但如果是自定义的数据类型(用 struct 或者 class 实现),直接用 constexpr 修饰是不行的。
修饰函数
- 函数必须要有返回值,并且 return 返回的表达式必须是常量表达式。
- 函数在使用之前,必须有对应的定义语句。
- 整个函数的函数体中,不能出现非常量表达式之外的语句(using 指令、typedef 语句以及 static_assert 断言、return 语句除外)。
constexpr int func2()
{
using mytype = int;
constexpr mytype a = 100;
constexpr mytype b = 10;
constexpr mytype c = a * b;
return c - (a + b);
}
修饰模板函数
Cpp11 语法中,constexpr 可以修饰函数模板,但由于模板中类型的不确定性,因此函数模板实例化后的模板函数是否符合常量表达式函数的要求也是不确定的。如果 constexpr 修饰的模板函数实例化结果不满足常量表达式函数的要求,则 constexpr 会被自动忽略,即该函数就等同于一个普通函数。
#include <iostream>
using namespace std;
struct Person {
const char* name;
int age;
};
// 定义函数模板
template<typename T>
constexpr T dispaly(T t) {
return t;
}
int main()
{
struct Person p { "luffy", 19 };
//普通函数
struct Person ret = dispaly(p);
cout << "luffy's name: " << ret.name << ", age: " << ret.age << endl;
//常量表达式函数
constexpr int ret1 = dispaly(250);
cout << ret1 << endl;
constexpr struct Person p1 { "luffy", 19 };
constexpr struct Person p2 = dispaly(p1);
cout << "luffy's name: " << p2.name << ", age: " << p2.age << endl;
return 0;
}
在上面示例程序中定义了一个函数模板 display(),但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:
- struct Person ret = dispaly§; 由于参数 p 是变量,所以实例化后的函数不是常量表达式函数,此时 constexpr 是无效的
- constexpr int ret1 = dispaly(250); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
- constexpr struct Person p2 = dispaly(p1); 参数是常量,符合常量表达式函数的要求,此时 constexpr 是有效的
修饰构造函数
也可以使用 constexpr 修饰一个构造函数,这样就可以得到一个常量构造函数了。常量构造函数有一个要求:构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值。
struct Person03 {
constexpr Person03(const char* p, int age) :name(p), age(age)
{
}
const char* name;
int age;
};
void constexpr_03(void)
{
constexpr struct Person03 p1("luffy", 19);
cout << "luffy's name: " << p1.name << ", age: " << p1.age << endl;
}
auto
Cpp11 中 auto 并不代表一种实际的数据类型,只是一个类型声明的 “占位符”,auto 并不是万能的在任意场景下都能够推导出变量的实际类型,使用auto声明的变量必须要进行初始化,以让编译器推导出它的实际类型,在编译时将auto占位符替换为真正的类型。
推导规则
auto 还可以和指针、引用结合起来使用也可以带上 const、volatile 限定符,在不同的场景下有对应的推导规则,规则内容如下:
当变量不是指针或者引用类型时,推导的结果中不会保留 const、volatile 关键字
当变量是指针或者引用类型时,推导的结果中会保留 const、volatile 关键字
nt temp = 110;
auto *a = &temp;
auto b = &temp;
auto &c = temp;
auto d = temp;
-
变量 a 的数据类型为 int*,因此 auto 关键字被推导为 int类型
-
变量 b 的数据类型为 int*,因此 auto 关键字被推导为 int* 类型
-
变量 c 的数据类型为 int&,因此 auto 关键字被推导为 int类型
-
变量 d 的数据类型为 int,因此 auto 关键字被推导为 int 类型
int tmp = 250;
const auto a1 = tmp;
auto a2 = a1;
const auto &a3 = tmp;
auto &a4 = a3;
-
变量 a1 的数据类型为 const int,因此 auto 关键字被推导为 int 类型
-
变量 a2 的数据类型为 int,但是 a2 没有声明为指针或引用因此 const 属性被去掉,auto 被推导为 int
-
变量 a3 的数据类型为 const int&,a3 被声明为引用因此 const 属性被保留,auto 关键字被推导为 int 类型
-
变量 a4 的数据类型为 const int&,a4 被声明为引用因此 const 属性被保留,auto 关键字被推导为 const int 类型
auto 的限制
-
不能作为函数参数使用。因为只有在函数调用的时候才会给函数参数传递实参,auto 要求必须要给修饰的变量赋值,因此二者矛盾。
-
不能用于类的非静态成员变量的初始化
-
不能用于类的非静态成员变量的初始化
-
无法使用 auto 推导出模板参数
decltype
decltype和auto都可以进行自动类型推导,但他们有几个差异点:
(1). auto需要初始化操作, decltype 除推导引用外不需要初始化.
(2). auto 推导表达式类型时计算了表达式的值, decltype只是推导出表达式返回类型,并不只算标定式的值.
(3). auto 忽略变量的const性质, decltype则保留变量的const性质.
(4). 对于引用, auto 推导出引用的原有类型, 而decltype则推导出引用
用法如下:
int val = 5;
decltype(val) val1 = 6;//val1为int类型, 初始化赋值为6
decltype(val) val2; //val2为int类型, 可以不用初始化
//auto val3;//编译不通过, 因为auto必须初始化
const int vala = 7;
decltype(vala) vala1 = 6;//vala1为const int类型, 初始化赋值为6
// vala1 = 8; //编译不通过
auto vala2 = vala;
vala2 = 8; //编译通过,因为vala2类型为int类型而不是const int
int valb = 8;
int& valb1 = valb;
decltype(valb1) valb2 = valb; //valb2为valb的引用
valb2 = 10;
std::cout<<"valb: "<<valb<<std::endl;//此时valb == 10
auto valb3 = valb;
valb3 = 11;
std::cout<<"valb: "<<valb<<std::endl;//此时valb仍为10,
//因为valb3不是valb的引用, valb3值的改变并不影响valb的值
auto fun = [](auto a, auto b)->int{
int sum = a + b;
std::cout<<"sum: "<<sum<<std::endl;
return sum;
};
auto fun1 = fun(1,2);
std::cout<<"fun1: "<<fun1<<std::endl;//此时fun1类型为int, 值为3
//并打印出sum: 3,表示计算了表达式的值并返回给fun1
decltype(fun(1,2)) fun2;
std::cout<<"fun2: "<<fun2<<std::endl;//此时fun1类型为int, 值为0
//并不打印出sum: 3, 表示未计算表达式的值
“=default” 、“=delete”
编译器默认为一个类生成的默认函数
- 默认构造函数
- 默认析构函数
- 默认拷贝构造函数
- 默认赋值函数
- 移动构造函数
- 移动拷贝函数
class DataOnly {
public:
DataOnly () // default constructor
~DataOnly () // destructor
DataOnly (const DataOnly & rhs) // copy constructor
DataOnly & operator=(const DataOnly & rhs) // copy assignment operator
DataOnly (const DataOnly && rhs) // Cpp11, move constructor
DataOnly & operator=(DataOnly && rhs) // Cpp11, move assignment operator
};
=delete
- 禁止使用编译器默认生成的函数
假如上面的几个函数中,不想使用其中某个,可以将其定义为private,或者使用=delete。
#include <iostream>
using namespace std;
class DataOnly {
public:
DataOnly () {}
~DataOnly () {}
DataOnly (const DataOnly & rhs) = delete; //禁止使用该函数
DataOnly & operator=(const DataOnly & rhs) = delete; //禁止使用该函数
DataOnly (const DataOnly && rhs) {}
DataOnly & operator=(DataOnly && rhs) {}
};
int main(int argc, char *argv[]) {
DataOnly data1;
DataOnly data2(data1); // error: call to deleted constructor of 'DataOnly'
DataOnly data3 = data1; // error: call to deleted constructor of 'DataOnly'
return 0;
}
- delete 关键字可用于任何函数,不仅仅局限于类的成员函数
#include <iostream>
using namespace std;
class DataOnly {
public:
void fun(int a) {}
void fun(float a) = delete;
};
int main(int argc, char *argv[]) {
DataOnly data1;
data1.fun(1); // OK
data1.fun(0.5); // error: call to member function 'fun' is ambiguous
return 0;
}
- 模板特化:在模板特例化中,可以用delete来过滤一些特定的形参类型
例如:Widget 类中声明了一个模板函数,当进行模板特化时,要求禁止参数为 void* 的函数调用。 - 按照私有不实现思路,应该是将特例化的函数声明为私有型,如下:
#include <iostream>
using namespace std;
class Widget {
public:
template<typename T>
void ProcessPointer(T* ptr) {
cout << typeid (T).name() << endl;
}
private:
void ProcessPointer(void*) {
cout << "void" << endl;
}
};
int main(int argc, char *argv[]) {
Widget w;
int* ip = NULL;
w.ProcessPointer(ip);
void* vp = NULL;
w.ProcessPointer(vp); //error: 'ProcessPointer' is a private member of 'Widget' w.ProcessPointer(vp);
return 0;
}
=delete直接添加在后面就可以解决这个问题
= defaule
在程序员重载了自己上面提到的Cpp编译器默认生成的函数之后,Cpp编译器将不在生成这些函数的默认形式。
但是Cpp允许我们使用=default来要求编译器生成一个默认函数,如
struct Point {
Point()=default;
Point(int _x, int _y) : x(_x), y(_y) {}
int x = 0;
int y = 0;
}
这样相当于无参的构造函数也是可以使用的。