《C++ Primer》读书笔记第二章-2-复合类型 And const限定符

笔记会持续更新,有错误的地方欢迎指正,谢谢!

复合类型

常用复合类型:引用和指针。

引用

引用不是对象,所以不能定义引用的引用 且 定义时必须初始化;它就是个绰号,但对它的操作都是在与之绑定的对象上进行的;引用一旦定义,无法再绑定到其他对象。即是一个很专一的绰号。
例题1:
int &temp = 10; //错,引用的初始值必须是一个对象,不能是字面值或表达式的结果(比如:int &temp = 10+11;)。
例题2:
double a = 10;
int &ref3 = a+10;//错,此引用所绑定的对象必须是int型的对象

指针

与引用不同,它是一个对象,只不过它的值只能是其他对象的地址。

-相关操作符:
&为取地址符:&在等号左边表示在定义引用,在等号右边表示在取地址。
*为解引用符:定义指针有两种写法(&同理):*可靠近变量亦可靠近数据类型。常用写法int *p;//不易被误解
例子:int* p1,p2;//p1是指向int的指针,p2却是整型

-空指针:
不指向任何对象,用nullptr初始化即可得到,但尽量别用空指针,最好初始化所有指针。

-初始化指针:建议用已定义对象或nullptr或0初始化所有指针,把任何int型变量(即使值为0)赋值给指针是错误的

-指针和引用的区别:
都能提供对其他对象的间接访问,但最大的区别是引用本身并非一个对象,一旦定义了引用,就无法令其再绑定到其他对象;指针本身作为一个对象,它存对象的地址,朝三暮四。

理解复合类型的声明

-定义多个变量
例子:int* p1,p2;int *p1,*p2;
-二级指针
指向指针的指针
-指针的引用
指针的引用格式:*&
例子:
int *p;
int *&r = p;//r是对指针p的引用

const限定符

const基础知识

const对象一旦创建后其值就不能再改变。 因为你记住的这一点,所以const对象必须初始化。为什么呢,因为你不能改变const的值,而你在定义的时候又没有给它一个初值,那要它干嘛?
例子1:
const int i = 0;//正确,编译时初始化。
const int i=Get_num();//正确,运行时初始化亦可。
例子2:
int i = 42;
const int ci = i;//正确,ci初始化为对象i。
int j = ci;//正确,ci的值拷贝给j,并没有改变ci的值,j也与i无关。
ci = j;//错误,企图改变常量ci的值。

const对象仅在文件内有效,如果想在多个文件之间共享编译时初始化的const对象(即常量表达式),在其他文件正常声明后使用即可。而如果想在多个文件之间共享运行时初始化的const对象(即函数返回值),必须在变量的定义和声明前部都添加extern关键字。都只需定义一次就好了,你可以选择记住它。
//file1.h
extern const int temp=Func();
//在flie2.h里声明便可在该文件里使用了
extern const int temp;

const和引用

-定义:
把引用绑定到const对象上,我们称之为对常量的引用,又叫常量引用,格式为:const 变量类型 &变量名。与普通引用不同的是,不能通过此种引用修改它绑定的对象。
-用途:
常量引用常用于:形参和实参,保证 函数内形参不可更改,也就是保证传入的实参为常量。
-常量引用的例子:
1. const int ci = 12;
2. const int &r1 = ci;//正确,引用是常量,引用的对象也是常量。
3. r1 = 42; //错误,r1是对常量ci的引用,不能修改它的值。
4. int &r2 = ci;//错误,试图让一个非常量引用指向一个常量对象,即非常量引用 不能–> 常量对象
由第4题可推出:另外三种情况却都是正确的,如下:
1. 非常量引用 能–> 非常量对象
2. 常量引用 能–> 常量对象
3. 常量引用 能–> 非常量对象//const可绑定非const
-常量引用的补充:对于第3种情况:常量引用绑定的对象若为非常量对象,则此非常量对象肯定可被修改。例子如下:
int ci = 12;
const int &r = ci;
问题:若改变ci的值后,r的值会变吗?
正确答案:会!因为:引用不是对象,所以并不存在常量引用,没法让引用本身恒定不变。

-初始化和常量引用
被引用的对象和引用类型不同时,也就是需要类型转换时,就会创建一个临时变量。
例子:
double pi = 3.14;
const int &ri = pi; //正确
此处ri应该引用一个const int型的数,但pi是double,因此,编译器是这么干的(通过临时变量temp默认改变类型并传递其值):
const int temp = pi;
const int &ri = temp; //当然输出ri的话就是3了。
也就是说ri其实没有绑定到pi上,而是绑定到了一个临时对象上,那么下面这种情况合法吗?
double pi = 3.14;
int &ri = pi; //错误。
不讲道理的原因:两变量类型都不一样,何以是同一个对象?
讲道理的原因:如果合法的话,程序员肯定想用ri去改变pi的值,而实际上ri是绑定到了一个临时变量,根本改变不了pi的值,所以就错误了。

const和指针

严重声明:

《C++ Primer》中对于“常量指针”的定义和网上的版本相反,而且删去了”指针常量”的概念,引入了一个新的概念——”指向常量的指针”,但各种类型的含义还是一致的,只是叫法不一样了,这里以网上的叫法为准。
1. 常量指针(书上叫 指向常量的指针)(底层const):自觉不去(靠它自己也不能)改变所指对象,而该对象若不是常量对象则其值通过其他方式改变所指对象。
常量指针的例外:
double pi=3.14;
const double *val=π
常量指针并未规定所指的对象是一个常量,如上例子所指的对象就是一个非常量,和常量引用一样 通过临时量进行默认的类型转换并成为了一种例外。所以,常量指针和常量引用一样仅仅要求不能通过 指针/引用 去改变 所指/所引 对象的值,而未规定那个对象不能通过其他方式被改变。
总结:其实常量指针和常量引用的 所指/所引 对象能被改变,只是不能通过他们罢了!总之,const可绑定非const。
2. 指针常量(书上叫 常量指针)(顶层const):由于指针为常量,所以声明时必须初始化,且初始化后存放在指针中那个地址不可改变。但此地址对应的数可被改变:指针常量若所指对象是常量,指针常量不能修改其所指对象的值;指针常量若所指对象是非常量,指针常量也能通过指针修改其所指对象的值。

来看一道const和指针的面试题

const char *a和char const *a和char * const a和const char * const a

前者声明(*a),(*a)是const char类型的。a被一个解引用运算符修饰,故a是个普通的指针,可以修改,但是a所指向的数据(即*a)由于const的修饰而不可通过指针a去修改。
后者声明(*const a),(*const a)是char类型的。a被一个解引用运算符和一个const关键词修饰,故a是个不可修改的指针,但可通过指针a去修改a所指向的数据(即*a)。
char const *a和const char *a是同一个意思。
如果既不允许a被修改,也不允许a所指向的数据被修改,那么需要声明为const char * const a。

总结:别误认为指针常量就不能改所指的值了,所不能改的应该是所指的地址。

const和指针、引用 总结

引用/指针类型匹配:除特例(const可绑定非const,基类可绑定派生类)外,指针和引用的类型都需要与之绑定的对象严格匹配
复合类型判断:从右向左阅读复杂的指针或引用的声明语句,离变量名越近的符号对变量的类型有越直接的影响

顶层const

C++的设计者搞出了两个概念:顶层const和底层const。顶层和底层是修饰const的。
-顶层const:本身是常量,即前面讲的常量指针;本身可为大多数据类型(如算术类型、类、部分指针等)。
-底层const:另一部分指针所指的对象是常量 和 各种引用所引的对象是常量(也就是用于声明引用的const)。

问题1:上述的部分指针另一部分指针应该怎么解释呢?
答案:判断指针类型的const是顶层const还是底层const的常用方法(只取决于*和const的相对位置):
1. 部分指针:const在*的左边。
2. 另一部分指针:const在*右边。
补充另外一种也正确的判断方法:const修饰其左边的“量”,左无则右。“量”为*则指针为常量指针,属于顶层const;“量”为类型则所指的对象是常量,属于底层const。

问题2: 各种引用都为底层const?
答案:引用不是对象,无本身。即是说:所引的对象为常量。

这内容有点绕哈,理解不了的,记住结论就好啦!

例子:
int i = 0;
int *const p1 = &i;//顶层
const int ci = 42; //顶层
const int *p2 = &ci;//底层
const int *const p3 = p2; //靠右的顶层,靠左的底层
const int &r = ci; //底层

执行拷贝操作时,顶层const对被拷贝对象无影响,因为执行拷贝操作时并不会改变被拷贝对象的值。

constexpr和常量表达式

常量表达式(const expression)是指数据类型和初始值都需要是常量类型的,值不会改变并在编译过程就能得到计算结果的表达式。
例子:
const int a = 20;//是
const int b = a+1;// 是
int c = 27; //不是,因为不是const int
const int d = get_size(); //不是,因为在运行时才能得到d的值。
constexpr变量
一定是常量,必须用常量表达式(字面值类型,包括算术类型、引用、指针)或constexpr函数(constexpr函数是足够简单以使得编译时就可以计算其结果)初始化。
例子:
constexpr int a = 20;//对
constexpr int b = a + 1;//对
constexpr int sz = size();//可对可错,只有当size是一个constexpr函数时才正确。
一般来说,如果你认定变量是一个常量表达式,那就把它声明为constexpr类型。constexpr变量一般用于算术类型;指针和引用虽能用,但其初始值必为nullptr或0,或者某固定地址的对象,初始值受到了严格地限制。
指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对*(指针)有效,即永远将对象置为顶层const。
例子:
const int i = 0;
const int *p = &i; //p是一个指向整型常量的指针(const底层)
constexpr int *q = &i; //q是一个指向整型的指针常量(const顶层)
注意:i得定义在函数体之外(毕竟要在编译时就确定q的值)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值