1 auto
1.1 auto的作用
编程时候常常需要把表达式的值付给变量,需要在声明变量的时候清楚的知道变量是什么类型。然而做到这一点并非那么容易(特别是模板中),有时候根本做不到。为了解决这个问题,C++11新标准就引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。和原来那些只对应某种特定的类型说明符(例如 int)不同。auto 让编译器通过初始值来进行类型推演。从而获得定义变量的类型,所以说 auto 定义的变量必须有初始值。
auto item = val1 + val2;
auto red = LayerColor::create(Color4B(255, 100, 100, 255), 200, 100);
如果val1和val2都是int类型, 那么item也是int类型, 如果val1和val2是double类型, 那么item就是double类型. 而create()的返回值类型有可能会很复杂, 我们可以不用管, 这就使我们编程更加方便简单
1.2 auto和const
auto会忽略掉顶层const, 保留底层const. 举例:
const int i = 5;
auto a = i; // 变量i是顶层const, 会被忽略, 所以b的类型是int
auto b = &i; // 变量i是一个常量, 对常量取地址是一种底层const, 所以b的类型是const int *
因此, 如果希望推断出的类型是顶层const的, 那么就需要在auto前面加上cosnt:
const auto c = i;
1.3 auto和引用
- 如果表达式是引用类型, 那么auto的类型是这个引用的对象的类型.
int i = 2, &ri = i;
auto k = ri; // k是int类型, 而不是引用类型
- 如果要声明一个引用, 就必须要加上&, 如果要声明为一个指针, 既可以加上也可以不加
int i = 3;
auto &refi = i; // refi是一个int类型的引用
auto *p1 = &i; // 此时推断出来的类型是int, p1是指向int的指针
auto p2 = &i; // 此时推断出来的类型是int*, p2是指向int的指针
2 decltype
2.1 decltype的作用
有的时候我们还会遇到这种情况,我们希望从表达式中推断出要定义变量的类型,但却不想用表达式的值去初始化变量。还有可能是函数的返回类型为某表达式的的值类型。在这些时候auto显得就无力了,所以C++11又引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。在此过程中,编译器只是分析表达式并得到它的类型,却不进行实际的计算表达式的值。
decltype(func()) sum = x; // sum的类型是函数func()的返回值的类型, 但是这时不会实际调用函数func()
int i = 0;
decltype(i) j = 4; // i的类型是int, 所以j的类型也是int
2.2 decltype和const
不论是顶层const还是底层const, decltype都会保留
const int i = 3;
decltype(i) j = i; // j的类型和i是一样的, 都是const int
2.3 decltype和引用
- 如果表达式是引用类型, 那么decltype的类型也是引用
const int i = 3, &j = i;
decltype(j) k = 5; // k的类型是 const int &
- 如果表达式是引用类型, 但是想要得到这个引用所指向的类型, 需要修改表达式:
int i = 3, &r = i;
decltype(r + 0) t = 5; // 此时是int类型
- 对指针的解引用操作返回的是引用类型
int i = 3, j = 6, *p = &i;
decltype(*p) c = j; // c是int类型的引用, c和j绑定在一起
- 如果一个表达式的类型不是引用, 但是我们需要推断出引用, 那么可以加上一对括号, 就变成了引用类型了
int i = 3;
decltype((i)) j = i; // 此时j的类型是int类型的引用, j和i绑定在了一起
2.4 auto与decltype的差别
- decltype和auto都可以用来推断类型,但是二者有几处明显的差异:
- auto忽略顶层const,decltype保留顶层const;
- 对引用操作,auto推断出原有类型,decltype推断出引用;
- 对解引用操作,auto推断出原有类型,decltype推断出引用;
- auto推断时会实际执行,decltype不会执行,只做分析。
总之在使用中过程中和const、引用和指针结合时需要特别小心。
3 constexpr
3.1 运行时常量和编译时常量
c++中常量表示该值不可修改,通常是通过const关键字修饰,比如
const int i = 3;
大多数情况下const描述的都是运行时常量,即运行时数据不可更改。
常量表达式主要是允许一些计算发生在编译时,即发生在代码编译阶段而不是代码运行阶段。这是很大的优化,因为如果有些事情可以在编译时做,那么它只会做一次,而不是每次程序运行时都计算。
3.2 常量表达式
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <vector>
#include <map>
/**
* 常量表达式主要是允许一些计算发生在编译时,即发生在代码编译而不是运行的时候。
* 这是很大的优化:假如有些事情可以在编译时做,它将只做一次,而不是每次程序运行时都计算。
*/
/*
constexpr函数的限制:
1:函数中只能有一个return语句(有极少特例)
2:函数必须返回值(不能是void函数)
3:在使用前必须已有定义
4:return返回语句表达式中不能使用非常量表达式的函数、全局数据,且必须是一个常量表达式
*/
constexpr int GetConst()
{
return 3;
}
//err,函数中只能有一个return语句
constexpr int data()
{
constexpr int i = 1;
return i;
}
constexpr int data2()
{
//一个constexpr函数,只允许包含一行可执行代码
//但允许包含typedef、 using 指令、静态断言等。
static_assert(1, "fail");
return 100;
}
int a = 3;
constexpr int data3()
{
return a;//err, return返回语句表达式中不能使用非常量表达式的函数、全局数据
}
/*
常量表达式的构造函数有以下限制:
1:函数体必须为空
2:初始化列表只能由常量表达式来赋值
*/
struct Date
{
constexpr Date(int y, int m, int d): year(y), month(m), day(d) {}
constexpr int GetYear() { return year; }
constexpr int GetMonth() { return month; }
constexpr int GetDay() { return day; }
private:
int year;
int month;
int day;
};
void mytest()
{
int arr[GetConst()] = {0};
enum {e1 = GetConst(), e2};
constexpr int num = GetConst();
constexpr int func(); //函数声明,定义放在该函数后面
constexpr int c = func(); //err, 无法通过编译, 在使用前必须已有定义
constexpr Date PRCfound {1949, 10, 1};
constexpr int foundmonth = PRCfound.GetMonth();
std::cout << foundmonth << std::endl; // 10
return;
}
constexpr int func()
{
return 1;
}
int main()
{
mytest();
system("pause");
return 0;
}
4 nullptr
熟悉C++的童鞋都知道,为了避免“野指针”(即指针在首次使用之前没有进行初始化)的出现,我们声明一个指针后最好马上对其进行初始化操作。如果暂时不明确该指针指向哪个变量,则需要赋予NULL值。除了NULL之外,C++11新标准中又引入了nullptr来声明一个“空指针”,这样,我们就有下面三种方法来获取一个“空指针”,如下:
int *p1 = NULL;
int *p2 = 0;
int *p3 = nullptr;
新标准中建议使用nullptr代替NULL来声明空指针。
4.1 C/C++中的NULL到底是什么
- NULL在C++中的定义,NULL在C++中被明确定义为整数0:
/* Define NULL pointer value */
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else /* __cplusplus */
#define NULL ((void *)0)
#endif /* __cplusplus */
#endif /* NULL */
- NULL在C中的定义,在C中,NULL通常被定义为如下:
#define NULL ((void *)0)
C++之所以讲NULL设置为0,根本原因和C++的函数重载机制有关。考虑下面这段代码:
void Func(char *);
void Func(int);
int main()
{
Func(NULL);
}
如果C++让NULL也支持void *的隐式类型转换,这样编译器就不知道应该调用哪一个函数。
4.2 ### 为什么要引入nullptr
C++把NULL定义为0,解决了函数重载后的函数匹配问题,但是又引入了另一个“问题”,同样是上面这段代码:
void Func(char *);
void Func(int);
int main()
{
Func(NULL); // 调用Func(int)
}
所以从程序员的角度来看,Func(NULL)应该调用的是Func(char *)但实际上NULL的值是0,所以调用了Func(int)。nullptr关键字真是为了解决这个问题而引入的。
nullptr关键字用于标识空指针,是std::nullptr_t类型的(constexpr)变量。它可以转换成任何指针类型和bool布尔类型,但是不能被转换为整数。
char *p1 = nullptr; // 正确
int *p2 = nullptr; // 正确
bool b = nullptr; // 正确. if(b)判断为false
int a = nullptr; // error
5 explicit
5.1 隐式转换和显式转换
C++的类型转换分为两种,一种为隐式转换,另一种为显式转换。
隐式转换是系统跟据程序的需要而自动转换的。
被explicit
关键字修饰的类构造函数,不能进行自动地隐式类型转换,只能显式地进行类型转换。
类型的隐式转换
//1)算术表达式
int m = 10;
double n = m;//n = 10.0;隐式把m转为double类型
int m = 10;
float f = 10.0;
double d = m + f;//d = 20.0;隐式把m和f转为double类型
//2)赋值
int *p = NULL; //NULL(0)隐式转换为int*类型的空指针值
//3)函数入参
float add(float f);
add(2); //2隐式转换为float类型
//4)函数返回值
double minus(int a, int b)
{
return a - b; //返回值隐式转换为double类型
}
类对象的隐式转换
void fun(CTest test);
class CTest
{
public:
CTest(int m = 0);
}
fun(20);//隐式转换
显式转换也叫强制转换,是自己主动让这个类型转换成别的类型。
类型的显式转换
int m = 5;
char c = (char)m;//显式把m转为char类型
double d = 2.0;
int i = 1;
i += static_cast<int>(d);//显式把d转换为int类型
类对象的显式转换
当类构造函数只有一个参数或除了第一个参数外其余参数都有默认值时,则此类有隐含的类型转换操作符(隐式转换),但有时隐式转换并不是我们想要的,可在构造函数前加上关键字explicit
,来指定显式调用。
void fun(CTest test);
class CTest
{
public:
explicit CTest(int m = 0);
}
fun(20);//error 隐式转换
fun(static_cast<CTest>(20)); //ok 显式转换
5.2 显式转换操作符
#include <iostream>
using namespace std;
class ConvertTo
{
};
class Convertable
{
public:
explicit operator ConvertTo () const
{
return ConvertTo();
}
};
void Func(ConvertTo ct) { }
int main()
{
Convertable c;
ConvertTo ct(c); // 直接初始化,通过
// ConvertTo ct2 = c; // 拷贝构造初始化,编译失败
ConvertTo ct3 = static_cast<ConvertTo>(c); // 强制转化,通过
// Func(c); // 拷贝构造初始化,编译失败
}
定义了两个类型ConvertTo和Convertable,Convertable定义了一个显式转换到ConvertTo类型的类型转换符。那么对于main中ConvertTo类型的ct变量而言,由于其直接初始化构造于Convertable变量c,所以可以编译通过。而做强制类型转换的ct3同样通过了编译。但是ct2由于需要从c进行拷贝构造初始化,因而不能通过编译。此外,调用函数Func的时候,传入Convertable的变量c的也会导致实参进行拷贝构造初始化,因此也不能通过编译。
6 default和delete
=default::用于显式要求编译器提供合成版本的四大函数(构造、拷贝、析构、赋值) ,必须用在有默认合成函数的函数上。
=delete:用于定义删除函数,在旧标准下,如果希望阻止拷贝可以通过显式声明拷贝构造函数和拷贝赋值函数为private,新标准下允许我们定义删除函数。可以使用在任意的函数上,除了析构函数(析构函数为删除函数将导致该类型无法销毁)。
struct NoCopy{
NoCopy() = default;
NoCopy(const NoCopy&) = delete; // 阻止拷贝
NoCopy& operator=(const NoCopy&) = delete; //阻止赋值
~NoCopy() = default;
}
网友评论