【1】C++指针与引用、const、auto、decltype


之前看过一遍《C++ Prime Plus》,可是没有做笔记,一段时间后,基本也记不住了,最近结合相关视频,再次捡起来,结合此书做出笔记,不断查漏补缺。
基础概念平时显得微不足道,但不积跬步何以至千里…


参考资料:
[1] 《C++ Prime Plus》第五版


1、C++基本内置类型和变量


1.1 基本内置类型

C++的类型可以分为三类:

  1. 内置类型:最基本的类型,包括int,char,float,unsigned等;
  2. 复合类型:引用、指针和数组;
  3. 类类型:就是类,比如string以及自己定义的类。

1.2 变量

初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值(为其分配一块地址空间),而赋值的含义是把当前对象的当前值擦出,而以一个新值来代替(已经分配过地址空间了)

变量的定义和声明:
变量的声明只规定了变量的类型和名字;
而变量的定义不仅规定了变量的类型和名字,还为变量申请了存储空间,也可能会为变量赋一个初始值(初始化)。
变量能且只能被定义一次,但是可以被多次声明。
如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而不要显式地初始化变量:

extern int i;        //声明i而非定义i
int j;                //声明并定义j

如果声明进行了初始化,即使前面加上了extern关键字,也会被当做定义。

extern double pi = 3.14;        //定义

2、指针


2.1 指针基础


指针存放着某个对象的地址,要想获取该指针,需要使用操作符&。除极个别的情况,指针类型要和他所指向的对象严格匹配。

  • 指针本身就是一个对象,允许对指针的赋值和拷贝,而在指针的生命周期内他可以先后指向几个不同的的对象。
  • 指针无需在定时赋初值。和其他内置类型(如char、int、float) 一样,在块作用域内的指针如果没有被初始化,也将拥有一个不确定的值。
double dval;
double *pd = &dval;        //正确:初始值是double型对象的地址
double *pd2 = pd;        //正确:初始值是指向double对象的指针

int *pi = pd;            //错误:指针pi的类型和pd的类型不匹配
pi = &dval;                //错误:试图把double型对象的地址赋给int指针

指针的值应该属于以下四种状态之一:

  • 指向一个对象
  • 指向紧邻对象所占空间的下一位置
  • 空指针,指针没有指向任何对象(nullptr)
  • 无效指针,上诉情况以外的其他值,试图访问或者拷贝无效指针的值都将引起错误,而编译器并不负责检查此类错误
  • 任何非零的指针所对应的的条件值都是true

对于空指针,C++中有以下几种生成的方法:

int *p1 = nullptr;        //等价于int *p1 = 0;
int *p2 = 0;            //直接将p2初始为字面常量0
//需要首先include <cstdlib>
int *p3 = NULL;            //等价于int *p3 = 0;NULL为预处理变量(运行于编译之前)
//最好使用nullptr

void*指针:
void* 指针是一种特殊的指针,可以用于存放任意对象的地址。

double obj = 3.14,*pd = &obj;
void *pv = &obj;        //正确:void*能够存放任意对象的地址,obj可以是任意类型的对象
pv = pd;                //pv可以存放任意类型的指针

void*指针所能做的事情:

  • 和其余指针作比较
  • 作为函数的输入输出
  • 赋值给另外一个void*指针
  • void*指针所不能做的事情:

不能直接操作void*所指向的对象,因为并不知道找个对象到底是什么类型,总之,从void*的视角来看,内存空间仅仅只是内存空间,没有办法访问内存空间所存的对象。

2.2 顶层const和底层const

顶层(top-level)const:表示指针本身是一个常量。

底层(low-level)const:指针所指的对象是一个常量。

int i = 0;
int *const p1 = &i;	// 不能改变p1的值,p1为顶层const。
const int p2 = 42;	// 不能改变p2的值,p2为顶层const。
const int *p3 = &i;	// 能够改变p3的值,p3为底层const。
const int& p4 = p2;	// 用于申明引用的const都是底层const。

3、引用

在C语言中,指针可以说是家常便饭,但是指针的错误使用,将会造成不可估量的错误(会破坏其余内存的内容)。故在C++中引出了一个作用类似于指针的操作方法 — 引用。

引用: 为对象另起的一个别名(外号)。

实例:

int ival = 1024;
int &refval = ival;        //refval指向ival
int &refval2;            //错误,引用必须初始化
int &refval3 = 1024;    //错误,引用初始化必须是一个对象
double r3 = 1.2;
int &refval4 = r3;        //错误,此处引用类型的初始值必须是一个int类型对象

注意:

  • 引用仅是变量的别名,而不是一个实实在在的定义了一个变量。因此,引用并不占用内存,而是和目标变量共同指向目标变量的内存地址。
  • 取址符号&不在是取变量地址,而是用来表示该变量的引用类型的
  • 定义了一个引用,必须对其初始化,且初始化时初始值必须是一个对象,该对象的类型要和引用相一致
  • 对引用的修改就是对变量的修改
  • 引用本身不是一个对象,所以不能定义引用的引用

指针与引用的区别:
引用本身并不是一个对象,一旦定义了引用,就无法令其在绑定到另外的对象,之后每次使用这个引用都是访问它最初绑定的那个对象(从一而终)。而指针则没有这个区别,给指针赋值就是令它存放一个新的地址,修改这个值就能令指针指向一个新的对象。
总结:
相同点: 都是地址的概念
指针指向一块内存,它的内容是所指内存的地址;引用是某快地址的别名。
不同点:

  1. 指针是一个实体,而引用只是别名
  2. 引用使用时无需解引用(*),指针需要解引用
  3. 引用只能定义是被初始化,之后不能改变;而指针可变
  4. 引用没有const,指针有const,const指针不可以改变
  5. 引用不能为空,指针可以为空
  6. 指针与引用的自增(++)运算意义不相同

指针和引用的联系:

  1. 引用在语言内部用指针实现
  2. 对一般应用而言,把引用理解成指针,不会犯严重的错误。引用是操作受限了的指针(仅容许取内容操作)。

指向指针的引用:
引用本身不是对象,因此不能定义指向引用的指针。但是指针是对象,实实在在存在的,因此存在指针的引用。

int i =42;
int *p;                 //p是一个int类型的指针
int *&r = p;            //r是一个对指针p的引用
r = &r;                //r引用了一个指针,因此给e幅值&r就是令p指向i
*r = 0;                //解引用r得到i,也就是p指向的对象,将i的值改为0

4、const限定符

const对象一旦创建以后,其值就不能改变,所以const对象必须初始化。const对象初始化可以是任意的表达式。

const int i = get_size();        //正确:运行时初始化
const int  j = 42;                //正确:编译时初始化
const int k;                    //错误:k是一个未经初始化的常量

默认情况下,const对象仅在文件内有效: 当多个文件出现了同名的const变量时,等同于在不同文件中分别定义了独立的变量。如果想在多个文件之间共享const对象,必须在变量定义之前添加extern关键字。

const和define相比较,有什么优点?

  1. const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行安全检,而对后者只进行字符替换,没有类型安全检查,并且在字符替换时,可能产生想象不到的错误(边际效应)。
  2. 有些集成化的调试工具,可以对const进行调试,但是不能对宏常量进行调试

对const引用:(常量引用 reference to const) 把引用绑到const对象上。与普通引用相不同的是,对常量的引用不能够用来修改它所绑定的对象。

const int ci = 1014;
const int &r1 = ci;        //正确:引用及其对应的对象都是常量
r1 = 42;                //错误:r1是对常量的引用
int &r2 = ci;            //错误:试图让一个非常量引用指向一个常量对象

注意:引用的类型必须要和所引用的对象类型相一致,但是有两个情况例外

  1. 初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能够转换成引用类型即可
  2. 允许为一个常量引用绑定非常量的对象、字面值,甚至是一般表达式
int i =42;
const int &r1 = i;            //允许将const &绑定到一个非常量对象上(2.)
const int &r2 = 42;            //(2.)
const int &r3 = r1*2;        //正确:r3是一个常量引用
int &r4 = r1*2;                //错误:r4是一个普通的非常量引用

指针和const:
指针的类型必须与其所指对象的类型相一致。但是有两个例外:

允许令一个指向常量的指针指向一个非常量的对象
和常量饮用一样,指向常量的指针也没有规定其所指向的对象必须是一个常量。所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有滚定那个对象的值不能通过其他途径改变。

constexpr

常量表达式(const expression)是指不会改变(const)并且在编译过程中就能得到计算结果的表达式。

  1. 字面值是常量表达式

    字面值:一个改变的值,包括数字,字符,字符串。

    字面值类型:算术类型(整型(包括字符和布尔类型)and浮点型,引用,指针)

  2. 用常量表达式初始化的const对象也是常量表达式

  • 字面值是常量表达式
  • 使用常量表达式初始化的const对象也是常量表达式

字面值类型: 算数类型、引用和指针都属于字面值类型。

const int max_files = 20;                //max_files 是常量表达式
const int limit = max_files +1;        //limit是常量表达式
int staff_size = 27;                    //staff_size不是常量表达式
const int sz = get_size();                //sz不是常量表达式

5、类型处理

5.1 类型别名

有两种方法实现类型别名:

1. 使用关键字typedef

typedef double wages;        //wages是double的同义词
typedef wages base,*p;        //base是double的同义词,p是double*的同义词

2. 新标准规定了一种新的方法,使用类型别名(alias declaration)来定义类型别名
等同于把等号左侧的名字规定为右侧类型的别名。
如果某个类型别名指代的是复合类型或常量,那么他用到声明语句里就会产生意想不到的后果。

using SI = Sales_iteml;        //SI是Sales_item的同义词

5.2 auto类型说明符

  1. C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。auto定义的变量必须初始化
  2. 使用auto可以在一条语句当中声明多个变量,但该语句中所有变量的类型必须都是一样的,这与int、double等是一样的;
  3. 编译器会以引用对象的类型作为auto类型;
  4. auto一般会忽略顶层const,同时底层const会被保留下来;
  5. 设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。
//错误示范:
auto item;
item = val1 + val2;

//正确示范:
auto = val1 + val2;

//而int定义的变量可以不初始化(虽然这样非常不推荐)
int item;
item = val1 + value;//正确
auto i = 0,*p = &i;            //正确:i是整数、p是整型指针
auto sz = 0,pi = 3.14;        //错误:sz和pi的线、类型不一样

5.3 decltype类型指示符

decltype:选择并返回操作数的类型。 编译器分析表达式并得到它的类型,却不实际计算表达式的值。

decltype(f())  sum = x;        //sum的类型就是函数f的返回类型

操作数: 是运算符作用的实体,是表达是的一个组成部分,它规定了指令中进行数学运算的量。表达式是操作数和操作符的结合。

例如:a + b:a、b是操作数,+是操作符

  • 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。
  • 如果表达式的内容是解引用操,则decltype将得到引用类型
/* decltype的结果可以使引用类型 */
int i = 42,*p = &i,&r = i;
decltype(r+0) b;                //正确:加法的结果是int,因此b是一个未初始化的int
                                //局部变量未初始化值随机、全局变量未初始化值为0
decltype(*p) c;                    //错误:c为引用,引用必须使用对象进行初始化、
decltype(*p + 0) d;                //正确:加法的结果是int,因此d是一个未初始化的int

decltype和auto的重要区别:decltype的结果类型与表达式形式密切相关。对于decltype所用的表达式来说,如果变量名加上一对括号,则得到的类型玉不加括号时会有不同。如decltype使用的是一个不加括号的变量,得到的就是该变量的类型;如加上括号,编译器就会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型。

//decltype的表达式如果是加了括号的变量,结果将是引用
decltype((i)) a;            //错误:a是int&,必须使用对象进行初始化
decltype(i) b;                //正确:e是一个(未初始化的)int

切记:
decltype((variable))(注意是双层括号)的结果永远是引用,而decltype(variable)结果只有当variable本身是一个引用是才是引用。


 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值