定义顶层const表示指针本身是一个常量,底层const表示指针所指的对象是一个常量。
常量表达式是指不会改变且在编译过程中就能得到计算结果的表达式。包括字面值和用常量表达式初始化的const对象。
const int a=20;
//是
const int b=a+1;
//是,且无论a是否为常量
int c=20;
//否,数据类型非const
const int d=get_number();
//否,具体值直到运行才能获取到
若需要一个变量为一个常量表达式,那就把它声明成constexpr类型。
constexpr int a=20
//是
constexpr int b=a+1;
//是,且无论a是否为常量
constexpr int c=get_number();
//只有当get_number()是constexpr函数时正确
注:constexpr仅支持算术类型、指针和引用(三者属于字面值类型),而自定义类如string,IO库等则不能。另外constexpr指针的初始值必须是nullptr或0,或存储在固定地址中的对象—–这意味着函数体内的变量一般不能被constexpr指针指向。
再注:constpr限定符仅对指针本身有效,对指针所指向的对象无关:
const int *p=nullptr;
//p是一个指向整型常量的指针
constexpr int *q=nullptr;
//q是一个指向整数的常量指针
constexpr函数的返回值及所有形参的类型都得是字面值类型且函数体中必须有且只有一条return语句,但其返回值不要求一定是常量表达式。
类型
类型别名typedef与using
typedef int a;
//a是int的同义词
typedef a b,*p;
//b是int的同义词,p是int*的同义词—是指向int的指针
using x=y;
x是y的同义词
auto类型说明符让编译器通过初始值来推算变量的类型,故而auto定义的变量必须有初始值。可以在一条auto语句中声明多个变量,但该句中所有变量的初始基本数据类型必须一样。
使用auto时注意以下情况:
const int i=10;
auto a=i;
//此时b被定义成了一个int类型
const auto b=i;
//此时b才被定义成了一个const int类型
auto c=&i;
//c是一个指向整数常量的指针
类型说明符decltype选择并返回操作数的数据类型,注意编译器只分析表达式并得到它的类型却不会实际计算表达式的值。decltype接受函数名、变量名、表达式、const和引用。
函数
decltype(func()) a=b;
//a的类型就是函数func的返回类型,但编译器不实际调用func
变量名
decltype(i) c=0;
//c的类型是i的数据类型即const int
const int &j=i;
decltype(j) d=c;
//d的类型是const int&引用,d绑定到c
表达式
int a=1,*p=&i,&r=i; double b=3.14;
decltype(a+b) c;
//c的类型是表达式a+b计算结果对应的double类型
decltype(r+0) d=0;
//d的类型是int非引用
decltype(*p) e=0;
//e的类型是引用int&而非int
注:decltype所用表达式加括号时编译器当成表达式,不加时才表示变量类型。
decltype(a) x;
//x的类型是a的类型int
decltype((a)) y=0;
//y的类型是引用int&(双层括号的结果永远是引用!!)
sizeof运算符:
1. 对引用类型执行sizeof运算得到被引用对象所占空间的大小
2. 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小
显示转换
在隐式类型转换中编译器只会执行一步类型转换,若程序隐式的使用了两种转换规则则会报错。
cast-name<type>(expression)
cast-name:static_cast、dynamic_cast、const_cast、reinterpret_cast
- static_cast:任何具有明确定义且不包含底层const的类型转换。可用于编译器无法自动执行的类型转换(如void*转换为int*)。
- const_cast:只能改变运算对象的底层const。(去const性质:将常量对象转换成非常量对象。只允许const_cast强制转换)
dynamic_cast:运行时编译器执行类型识别。包括以下三种形式
dynamic_cast<type*>(e)
//指针类型,e必须是一个有效的指针
dynamic_cast<type&>(e)
//引用类型,e必须是一个左值
dynamic_cast<type&&>(e)
// 引用类型,e不能是一个左值type必须是一个类类型,且通常情况下应含有虚函数。
e的类型是三者之一:目标type的公有派类、目标type的公有基类、目标type的类型。
注:左值指的是既能够出现在等号左边也能出现在等号右边的变量(或表达式),右值指的则是只能出现在等号右边的变量(或表达式)
静态局部对象(static)在程序的执行路径第一次经过对象定义语句时初始化,并且直到程序终止才被销毁,即使对象所在函数执行结束也不会对它有影响。
若局部静态变量没有显示的初始值,它将执行值初始化,内置类型的局部静态变量初始化为0。
函数
形参为const时忽略顶层const,如下:
void func(const int i){}
//func可以读取i但不能修改i
void func(int i){}
//错误,重复定义func(int)
尽量使用常量引用!!
我们不能把const对象、字面值或需要类型转换的对象传递给普通的引用形参。
由于数组的两个性质:
1. 不允许拷贝数组
2. 使用数组时(通常)会将其转换成指针
若我们传给函数的是一个数组,则实参自动地转换成指向数组首元素的指针,数组的大小对函数的调用没有影响。
数组的首元素和尾元素函数
#include<iterator>
//需要iterator头文件
int arr[]={0,1,2,3,4,5,6,7,8,9};
int *first=begin(arr),*last=end(arr);
//首元素和尾元素的下一个元素
范围for语句
for(auto &i : arr){}
//若需要对序列中的元素执行写操作,循环变量必须声明成引用类型。否则不用。
类
常量成员函数
class_name::func() const {}
//const用于修改隐式this指针的类型
紧跟在参数列表后面的const表示this是一个指向常量的指针,这意味着我们不能改变调用它的对象的内容,如下就是错误的:
string::count() const {length++;}
//错误,这里length为string数据成员,不能在常量成员函数里修改数据成员,只可调用。
特例:
mutable int length;
//mutable表示可变数据成员
string::count_mut() const {length++;}
//正确,const成员函数可以改变一个可变成员的值
常量对象、以及常量对象的引用或指针都只能调用常量成员函数。
要求编译器生成默认构造函数
class_name () = default;
构造函数初始化列表:
line::line(int start,int end):first(start),second(end) {}
若成员是const、引用或属于某种未提供默认构造函数的类类型,必须通过构造函数初始化列表为这些成员赋初值。
成员的初始化顺序与它们在类定义中的出现顺序一致,而与初始化列表中成员的出现顺序无关。
若需要限制构造函数定义的隐式转换,应使用关键字explicit
explicit class_name(const int&);
explicit关键字只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换。且只能在类内声明构造函数时使用explicit关键字,在类外部定义时不应重复。且explicit构造函数只能用于直接初始化。
如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化;
若不使用等号,则执行的是直接初始化。
聚合类:
1. 所有成员都是public的
2. 没有定义任何构造函数
3. 没有类内初始值
4. 没有基类,也没有virtual函数
聚合类使得用户可以直接访问其成员,并且具有特殊的初始化语法形式。如下是一个聚合类:
struct Data {
int first;
string second;
};
我们可以提供一个花括号的成员初始值列表,并用它初始化聚合类的数据成员:
Data a={0,"SECOND"};
//初始值的顺序必须与声明的顺序一致