C++ Primer 学习笔记一:引用、指针与常量

本系列主要记录我阅读C++ Primer一书时感觉需要总结的地方。本书第一章讲输入输出,比较简单,就不再专门记录。第二章讲变量,一些基础的内容略过,主要总结我感到比较重要也容易犯错的部分,即引用、指针和常量。本章其他一些内容也一并在内。本笔记只记录一些重要的内容,枝节被忽略。

变量的声明与定义

C++支持分离式编译,支持程序分为多个模块独自编译。为此,C++将声明(declaration)和定义(definition)分离。声明使得变量为程序所知,而定义创建与名字关联的实体,即申请存储空间并赋初值。

如果要声明而不定义一个变量,需要在变量名前添加extern关键字,且不能显式初始化变量。变量可以被声明多次而只能被定义一次。

声明符

一条声明由基本数据类型和声明符列表组成。一般来说声明符就是变量名。但对于像引用和指针这样的复合类型变量,声明则需要添加额外的内容。

声明引用时,需要在变量名之前添加&。声明指针前,需要在变量名前添加*。

注意不要把这里的&和*与操作符弄混。在用作操作符时,&的作用是取地址;*的作用是解引用,返回值是存储在该地址的变量。

引用

引用即别名。声明引用时,需要在变量名之前添加&。如果一条语句声明多个引用,则每个前都要加&。引用并非对象,也没有单独的存储位置,只是对一个对象起了另外的名字而已。引用必须被初始化,一旦绑定就不能再绑定到另一个对象上。引用必须绑定常量,而不是某个如3.14一样的值。(有例外)

如果绑定的对象不是常量,则可以通过引用修改被绑定变量的值,与直接赋值效果相同。

int &ref = i;  //正确
int& ref = i;  //这两种写法等价
int &ref2; 	   //错误,引用必须被初始化
int &ref3 = 3 //错误,引用不能绑定一个值
int &ref4 = i*2; //错误,引用不能绑定表达式
ref = 2;        //等价于i=2

指针

不同于引用,指针是一个单独的对象,指向的是某个变量的地址。声明指针前,需要在变量名前添加*。一条语句声明多个指针时,每个指针前都要加*。由于引用不是对象,没有地址,故不能定义指向引用的指针。

如果给指针赋值为0,则其为空指针,不指向任何对象。赋值为NULL或者nullptr也具有相同的效果。如果指针指向的不是常量,可以通过指针改变对应变量的值。除少数例外,指针类型必须要与指向对象的类型匹配。非常量指针可以重新赋值,指向别的对象。

特殊的是void*类型的指针可以指向任何对象,但不能对其进行操作。

int i = 0;
int *ip = &i;  //ip是指向i的指针
int* ip = &i;  //与上面的写法等价
int *ip2 = nullptr; //ip2是空指针,赋值为0或者NULL效果相同
*ip = 42;			//给i赋值为42,等效于i=42
ip = &j;            //ip变为指向j

指针也可以指向指针,可以通过在声明中增加*的个数来定义这样的类型。

也可以定义指针的引用。在声明时,从右往左阅读定义,离变量名最近的符号决定变量类型。

int *p;   //p是一个指针
int *&r = p;  //r是一个引用,是指针p的别名

常量

使用关键字const进行限定,可以得到常量。const对象一旦创建之后其值就不能被改变,所以必须要被初始化。

编译时,编辑器将文件中所有该变量都替换成对应的值。默认下,常量仅在文件内有效。在不同文件内定义同名const实际上是不同的变量。如果要在多个文件中贡献常量,需要用extern进行限定。在一个文件中定义,其他文件中声明并使用。每一处都要加上extern。

引用与常量

引用可以绑定到常量上。这时可以有一些例外情况。初始化常量引用时可以用任意表达式作为初始值,编译器会先将引用绑定一个临时值,再将初始值类型转换为临时值。

int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = i*2;
const double &r4 = i;
//上面语句都是正确的,但对于非常量引用则不合法

常量引用可以指向非常量,只是不能通过引用修改对象的值而已。

指针与常量

谈到指针与常量,就需要区分两个概念:常量指针与指向常量的指针。常量指针指的是指针本身就是一个常量,不能改变,即不能再指向别的对象。但可以通过常量指针改变其指向对象的值。指向常量的指针类似于常量的引用,说明不能通过该指针改变指向对象的值,其指向的也可以是一个非常量。如果要指向一个常量,则指针本身必须是指向常量的指针。

const double pi = 3.14;
double ptr = π     //错误,普通指针不能指向常量
const double *cptr = &pi //正确,但不同通过cptr给pi赋值
val = 3.14;
cptr = &val;   //正确,指向常量的指针也可以指向非常量
double *const p = &val;  //p是一个常量指针,将永远指向val
const double *const pip = π  //pip是一个指向常量的常量指针,永远指向pi
*p = 0;           //正确,可以通过常量指针改变非常量的值

顶层const

顶层const表示指针本身就是常量,底层const表示指针指向的是常量。在指针的声明中,靠右的是顶层。一般地,顶层const可以表示任何变量为常量。声明引用的const全部都是底层。

int i = 42;
int *const p1 = &i;  //顶层
const int a = 42;    //顶层
const int *p2 = &a;  //底层
const int *const p3 = p2; //右面的是顶层,左面的是底层

执行对象拷贝时,顶层const没有影响。但拷入和拷出的对象必须具有相同的底层const资格,或者对应的数据类型能够转换。非常量可以转换为常量,反之不行。

int *p = p3;   //错误,p无底层const
p2 = p3;       //正确
p2 = &i;       //正确,非常量可以转为常量
int &r = a;    //错误,常量不能变为非常量
const int &r2 = i; //正确

类型的处理

类型别名

可以通过typedef定义类型的别名。现在也可以通过using来声明。

typedef double wages;
using wages = double;

注意,复合变量或常量的类型别名会有很多复杂的结果。不能简单的将类型别名替换为其本来的名字进行解释。

typedef char *pstring    //这里的pstring实际上是char *的别名
const pstring cstr = 0;  //cstr是指向char的常量指针
const char *cstr = 0;    //这样简单替换理解是错误的
const pstring *ps;       //ps是指针,对象是指向char的常量指针

pstring的基本类型是指向常量的指针,在其前面加const表面其本身是个常量指针。而上面的错误示范中,char成了基本数据类型,而*却变成声明符的一部分,这种理解是错误的。最后一行里,基本数据类型是const pstring,即指向char的常量指针。

auto

auto可以让编译器替我们推断出变量类型,必须有初始值。auto从引用中推断出来的,是引用指向的对象的类型。即auto不会推断出来数据类型是引用。

auto一般会忽略顶层const。如果想要顶层const的话需要在auto前面加上。

decltype

decltype类型指示符返回操作数的作用类型。不同于auto,decltype可以返回引用,也会包括顶层const。

如果对象是表达式,则会返回运算的类型。加上括号或者多层括号会让操作符将括号内的内容当成表达式。

int i = 42, *p = &i, &r = i;
decltype(*p) a;  //a是一个引用
decltype(r) b;   //b是一个引用
decltype(i) c;   //c是一个int
decltype((i)) d;  //d是一个引用,双层括号结果一定是引用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值