C++ 变量和复合类型

简介: 前面说了C++的基本数据类型,下面来看看在C++中如何定义变量和常量。变量定义和初始化C++定义变量的方式和C语言一样,也可以在定义的同时初始化。值得一提的是列表初始化,在原来的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;
相关文章
|
25天前
|
存储 C++ 容器
学会在 C++ 中使用变量:从定义到实践
C++中的变量是数据容器,包括`int`、`double`、`char`、`string`和`bool`等类型。声明变量时指定类型和名称,如`int myNum = 15;`。`cout`与`&lt;&lt;`用于显示变量值。常量用`const`声明,值不可变。变量名应唯一,遵循特定命名规则,常量声明时需立即赋值。
112 1
|
25天前
|
算法 程序员 C语言
【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践(二)
【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践
54 0
【C++ 随机数分布类型 】深入探索C++随机数分布:原理、应用与实践(二)
|
29天前
|
编译器 C++
C++ 双冒号::开头的语法,::变量名,获取全局作用域变量
C++ 双冒号::开头的语法,::变量名,获取全局作用域变量
16 0
|
25天前
|
算法 编译器 数据库
【C++ 泛型编程 高级篇】使用SFINAE和if constexpr灵活处理类型进行条件编译
【C++ 泛型编程 高级篇】使用SFINAE和if constexpr灵活处理类型进行条件编译
243 0
|
25天前
|
机器学习/深度学习 算法 编译器
【C++ 泛型编程 中级篇】深度解析C++:类型模板参数与非类型模板参数
【C++ 泛型编程 中级篇】深度解析C++:类型模板参数与非类型模板参数
46 0
|
25天前
|
设计模式 程序员 C++
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
【C++ 泛型编程 高级篇】C++模板元编程:使用模板特化 灵活提取嵌套类型与多容器兼容性
241 2
|
25天前
|
存储 JSON 安全
【C++ 泛型编程 综合篇】泛型编程深度解析:C++中的五种类型泛型策略综合对比
【C++ 泛型编程 综合篇】泛型编程深度解析:C++中的五种类型泛型策略综合对比
65 1
|
11天前
|
存储 程序员 编译器
C++注释、变量、常量、关键字、标识符、输入输出
C++注释、变量、常量、关键字、标识符、输入输出
|
25天前
|
算法 测试技术 编译器
【C++ 基本类型 bool 】深入探索C++中的布尔类型Boolean(二 )
【C++ 基本类型 bool 】深入探索C++中的布尔类型Boolean
25 0
|
25天前
|
程序员 编译器 C语言
【C++ 基本类型 bool 】深入探索C++中的布尔类型Boolean(一)
【C++ 基本类型 bool 】深入探索C++中的布尔类型Boolean
37 0