C++ 变量和复合类型

前面说了C++的基本数据类型,下面来看看在C++中如何定义变量和常量。

变量

定义和初始化

C++定义变量的方式和C语言一样,也可以在定义的同时初始化。值得一提的是列表初始化,在原来的C++版本中可以用于初始化数组等。C++ 11标准增加了更广泛的列表初始化,所以可以用列表初始化来初始化单个变量。

//定义变量
int a;
//定义并初始化
int b = 1;
//同时定义多个变量
int c = 5, d = 6;
//C++ 11 新特性:列表初始化
int e{3};

列表初始化有一个限制,如果要初始化的值超过了可容纳的范围,就会引发编译错误,而直接赋值就可以。

//不能编译
//short s{3.1415};
//可以编译
short s = 3.1415;

对于函数内部的局部变量来说,如果不初始化的话,值是未定义的。对于未初始化的局部变量进行操作可能导致无法预料的后果。对于全局变量,如果没有给定初始值,默认值是0。

定义和声明变量

变量定义告诉编译器,我要创建一个变量,以后再用它。而变量声明告诉编译器,我要引用一个变量,所以你先按照这个变量的类型和名字去找它。声明变量需要使用extern关键字,而且声明的时候不能赋值。如果使用extern关键字并赋值,那么变量声明就变成了变量定义,而且这只能用于全局变量的声明和定义。如果对一个函数内部的本地变量声明添加初始化式,就会引发编译错误。

//定义了一个全局变量
int global_count;

//声明在另一个文件中定义的全局变量
extern int global_count;

标识符

标识符也就是变量、函数、类的名字,用于标识不同的对象。和大多数编程语言一样,C++的标识符需要以字母或下划线开头,有数组、字母和下划线组成,而且对大小写敏感。

作用域

如果一个标识符定义在花括号外面,那么这个标识符的作用域就是全局作用域。全局作用域的变量可以在本文件的任何地方访问,如果在其他文件中声明这个标识符,那么还可以在其他文件中访问。

如果一个标识符在某对花括号中定义,那么这个标识符的作用域就在这对花括号中,这就是局部作用域。局部作用域的标识符在超出这个块后,就无法被访问了。如果有一个全局变量,然后在某个作用域中又定义了一个同名变量,那么这个局部变量就会屏蔽对全局变量的访问。如果希望访问全局变量,需要使用域操作符::来指定。

//声明在另一个文件中定义的全局变量
extern int global_count;

void declare_and_define()
{
    cout << global_count << endl;
    if (true)
    {
        int global_count = 5;
        cout << "同名局部变量覆盖全局变量:" << global_count << endl;
        cout << "使用全局变量:" << ::global_count << endl;
    }
}

复合类型

复合类型指的是基于其他类型定义的更复杂的类型,这些复合类型也是C++语言的重点和难点。

指针

指针是C++语言从C语言中继承的类型。每个变量在内存中都有一个地址来存储,指针就是这个地址。利用指针我们可以直接对变量进行修改。定义指针需要在指针名前添加星号*。如果要在一行定义多个指针,那么每一个指针前都需要星号。

//指针
int *p1, *p2;

有了指针,还需要将变量的地址赋给它,这需要使用取地址符&。注意指针和变量的类型必须匹配,将int型变量的地址赋给double *类型的指针是错误的。

int d1 = 5, d2 = 6;
//指针
int *p1, *p2;
p1 = &d1;
p2 = &d2;
cout << "d1=" << d1 << ",p1=" << *p1 << endl;

如果想由指针访问其指向的对象,使用解引用符*。由于指针指向的是对象本身,所以使用解引用符修改对象会修改实际对象的值。

*p1 = 100;
cout << "d1=" << d1 << ",p1=" << *p1 << endl;

指针有两种状态,一种是指向某个内存空间,另一种是无效状态。对于无效状态的指针进行解引用会引发不可预料的后果,所以这种情况应该尽量避免。对于无效状态的指针,最好将其清空。在老版本的C++语言中,我们需要引用cstdlib头文件,并且使用其中预定义的NULL来清空指针,这个预定义的值实际上是0。在C++ 11标准中引入了一个新的字面量nullptr来代替NULL,所以在以后的程序中,我们最好使用nullptr

引用

引用是C++语言新增的一种类型,它和指针既有相似之处,也有不同之处。

先来看看如何定义引用。

int d1 = 5;
//d2是d1的引用
int& d2 = d1;

如果要在一行同时定义多个引用,需要在每个引用名前添加&

int &r1 = d1, &r2 = d2;

引用实际上是一个别名,一旦定义好,对引用的所有操作都相当于直接对原变量进行操作。这一点和指针很类似。

//修改引用也会修改原变量
d2 = 100;
cout << "d1=" << d1 << ",d2=" << d2 << endl;

但是需要注意一点,指针也是一个变量,所以一个指针可以多次赋值,指向不同变量的地址。而引用只能和一个变量绑定,所以引用在定义的时候必须初始化,而且一旦初始化,无法再绑定到其他变量。

复合类型总结

前面介绍了引用和指针两种复合类型,这些复合类型还可以互相组合,生成更加复杂的类型声明。对于指针和引用声明,它们是和变量组合在一起的。所以下面的定义中,p是一个指针,而d是一个变量。如果希望声明多个指针, 需要在每一个变量名前添加*号。

int *p, d;
//即使星号和类型放在一起,p仍然是指针,d仍然是变量
//int* p, d;

符合类型还可以互相组合。比如说,我们可以定义指针的指针。

int **pp = &p;

引用是一个别名,所以无法定义引用的指针。但是反过来可以定义指针的引用。

int*& r = p;

常量

常量定义

常量和变量一样,唯一的不同点是常量一经定义,它的值就不能够在改变。常量定义和变量差不多,只不过需要使用const限定符修饰。由于常量一经赋值就无法再改变,所以常量在定义的时候必须初始化。

//定义常量
const int const_count = 5;

编译器在处理常量的时候,会直接将常量替换为其对应的值,所以编译器需要知道常量的值。默认情况下,常量定义只在本文件中有效。如果在多个文件中定义了同名的常量,那么这些常量是各不相同的常量。如果需要在文件之间共享常量,就需要在常量定义和声明上都添加extern关键字。

变量的const引用

我们可以把引用标记为const的,这种情况下这个引用变为只读的,我们可以修改原变量,可以通过引用读取原变量,但是无法通过引用修改原变量。

//引用常量
int i = 5;
const int& r = i;
i = 10;
//r = 10;

指针常量

指针存储的就是对象的地址,如果我们把指针本身定义为const的,那么我们将无法将这个指针指向其他对象的地址,但是我们可以通过这个指针修改指向对象的值。需要注意,这种定义必须将const关键字置于紧挨的变量名的位置。

int j = 100;
//const指针
int *const cp = &i;
//可以修改指针指向的对象的值
*cp = 10;
//无法修改指针指向的对象
//cp = &j;

指向常量的指针

这种情况和上面那种情况正好相反,这次是将指针指向的对象声明为const的,这样一来,我们无法修改指针指向的对象的值,但是我们可以修改指针指向其他对象的地址。这种定义需要将const放到符合声明的最前面。虽然这种情况叫做指向常量的指针,但是这是对指针类型的声明,实际上这个指针完全可以指向一个变量,只不过我们无法通过指针修改这个变量的值。

const int *p = &i;
//可以修改指针指向其他对象
p = &j;
//无法通过指针修改值
//*p = 200;

顶层const和底层const

前面我们看到了,指针是一个非常复杂的话题。指针本身是一个对象,而它又指向另一个对象。这些情况和常量常量声明组合在一起,将会变得非常复杂。所以我们需要对其做出分类。我们把本身是const的对象叫做顶层const,而指向的对象是const的就叫做底层const。这样一来就比较清楚了,指向常量的指针就是底层const,而指针常量就是顶层const。

下面这种情况,变量ccp即使顶层const又是底层const。

//既是顶层const又是底层const
const int*const ccp = &i;

constexpr和常量表达式

有时候编译器要求程序中的某些值不能改变,而且必须在编译期就能计算出来,这样的值叫做常量表达式。显然,字面量和用常量表达式初始化的const对象都是常量表达式。

当然,一个变量并不是常量表达式,哪怕我们在程序中没有修改过变量的值也不行。一个用变量初始化的const对象也不是常量表达式。

C++ 11标准新规定了一个关键字constexpr,它可以让编译器检查声明的常量。如果这个常量不是合法的常量表达式,那么就无法编译。

//常量表达式
constexpr int MAX_COUNT = 100;
constexpr int MIN_COUNT = -MAX_COUNT;
//i不是常量,所以下面的代码不能编译
//constexpr int VARIABLE_COUNT = i;
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容