3196 字
16 分钟
C++基础三:变量、常量与复合数据类型
2025-03-01

1. 变量与常量#

使用声明语句可以声明一个变量

type name;

而变量可以先声明,后赋值,且可以多次赋值,但请确保使用前被赋值

C++中的变量有自己的作用域,我们只能在作用域中调用这个变量,对于不在任何代码块中的变量声明,即全局变量其作用域就是整个文件,否则就是所在最内层代码块,叫做局部变量

同一个标识符在同一个作用域中不能被多次声明,在不同且的作用域中被声明时,若二者作用域存在重复,则后声明的变量会覆盖掉前声明的变量(仅作用域覆盖,并不改变值),使前声明的变量处于暂时不可用的状态,直到后声明的变量离开作用域

在语句中声明的变量作用域就是这个语句

在变量的类型名前面加上const表示该变量不可变,即它是一个常量,常量必须在创建时被赋值,否则无法再次更改值

也可以在变量前加上volatile强调该变量可变,即使程序没有更改也可能发生更改,这是因为与硬件交互时,其他硬件可能会更改内存,而对于程序中没有更改的内存,编译器可能会进行优化,只访问一次内存,该关键字可以阻止编译器做这样的优化

C++中变量主要有以下三种存储方式

  • 自动存储

    在函数中定义的变量都会使用自动存储,自动存储的持续性与作用域相同,离开作用域后会立刻回收,自动存储的变量不会被自动初始化

  • 静态存储

    在函数外的全局变量,或者使用static关键字定义的变量会使用静态存储,静态存储的持续性与整个程序相同,即使离开作用域也不会被回收,其声明语句只会被执行一次,哪怕含有赋值语句

    IMPORTANT

    在不同作用域中的静态变量不会被视为同一个,只有同一个声明在循环中或者函数调用中被多次运行,这些声明才会被视为同一个变量,如

    static int a  = 1;
    a++;
    {
        static int a = 1;
        std::cout << a;
    }
    //输出1
    

    for(int i = 0;i < 3;i ++)
    {
        static int a = 1;
        a++;
        std::cout << a;
    }
    //输出234
    

    静态变量会被默认初始化为0

  • 动态存储

    new或者malloc分配的内存,其存储由程序管理,直到使用delete/free或者程序结束才会被回收,我们将在后面详细讨论这种方式

2. 复合类型#

2.1. 结构体#

结构体是一种将多个数据组合成一组的结构

结构体使用以下方法定义:

struct name{
    type1 name1;
    type2 name2;
};//注意分号结束

结构体中包含的变量可以是任何合法的变量,甚至是另一个结构体

结构体内变量的声明也可以加上赋值,这会在创建结构体时为他们赋予一个初始值

结构体中也可以包含函数,如果是一个与结构体同名切未表明返回值的函数会被用作构造函数,它会在创建结构体时被执行,但这些特性往往被更复杂的类使用,在结构体中很少使用

结构体定义之后可以和普通类型一样被声明,C语言中需要使用struct structname name,而C++中可以省略struct,使其表现与普通的类型一模一样

使用结构体时,可以使用.来像访问普通变量一样访问结构体的成员,如name.type1

结构体在编译后不会被保留,其表现和一堆(逻辑上)绑定在一起的变量无异,不用担心额外的开销

结构体的定义可以和声明合并,如

struct st{
    ...
} name;

这样就定义了一个结构体st,同时声明了一个对应st类型的变量name

这种情况下,也可以省略结构体的名称,使其成为匿名结构体,匿名结构体只能在定义时声明

结构体定义的作用域规则与变量一样,不过结构体定义本身不会执行任何操作,故它常常被放在函数外(作用域为整个文件)或者头文件中

当一个结构体的成员变量被设为const时,它便只能初始化而无法更改值,而如果一个结构体变量被设为const,它的所有成员变量都将不可变,我们可以将成员变量设为mutable,这样即使结构体变量是const,这个成员变量也能单独修改了

2.2. 联合#

联合,也叫共用体,是一种能存储多种数据类型的结构,与结构体不同,它实际上只是一个可能是不同种类的变量,并不会同时存储所有变量

定义联合的语法与结构体相似

union union_name{
    long long lvalue;
    int ivalue;
};

使用时语法也和结构体一样,不一样的是,它的所有成员指向相同的地址,实际上都是同一个,同时只能使用它的一个成员,否则会产生错误

显然,为了保证内存够用,联合的内存就是它内存最大的成员的内存,在上面的例子中就是long long的长度,哪怕使用ivalue时会有空闲空间

联合也可以匿名,甚至可以不要变量名,此时可以直接使用成员的名字调用联合的成员,当然,它们依然代表相同的内存空间

联合常用于存储可能是多种形式的数据,如可能是数字也可能是字符串的编号,也会用在一些内存限制极为严苛的地方,但是由于这种方法比较难用,容易产生问题,且现代的计算机也没有那么缺内存,故人们更倾向于直接定义多个不同类型的变量

结构体和联合可以相互嵌套使用

2.3. 枚举#

枚举是一种只有特定几种取值的结构

我们可以定义一个枚举

enum enum_name{value1,valu2,value3};

这样我们将定义一个只有3种取值的枚举

枚举可以像普通变量一样声明和赋值

enum_name e1 = value1;
e1 = value2;
std::cout << (e1==value2);//true

枚举常常被用来存储某些只有少数几种情况的设置,如输出的颜色,输出的格式等等

枚举的底层实现其实是整数,每个枚举值都会被赋予一个整数,当没有指定时,依次是0,1,2,3,...,我们可以在定义时显式指定对应值,如enum ename{a=1,b=10};,枚举的值不一定是连续的,编译器会根据枚举值的范围确定使用多大空间存储枚举

也因为如此,枚举可以被当作整数直接使用,但整数不能当作枚举量使,不过我们任然可以通过强制类型转换把整数转换成枚举,如果该整数对应某个枚举量,那么该枚举就是那个枚举量,否则,该枚举不等于任何枚举量,而如果整数超出了该枚举的值的范围,那就会引发未知的错误,总之,不要把整数值强制转换成枚举

2.4. 其它#

类、指针都是复合类型,但由于其内容较多,我们会在后面单独讨论

3. 存储连续性与连接性#

从上面可以看出,无论是基本类型还是复合类型,它们都遵守共同的作用域规则,而在作用域规则之外,变量有不同的存储方式与与外部共享的方式,我们叫做存储连续性与连接性。

存储连续性在变量一部分已经有介绍,主要有自动存储、静态存储和动态存储三种,而连接性主要针对全局变量(作用域为整个文件的变量)分为内部连接性和外部连接性,即不与外部共享和与外部共享

  • 内部连接性:自动变量、用static修饰的全局变量、全局常量
  • 外部连接性:没有static修饰的全局变量

没有static修饰的静态全局变量是外部连接性,这也是不能在头文件中包含变量定义的原因——头文件中没有static修饰的变量都会成为外部变量,它们都是外部连续性,在所有文件中可见,会引发多定义报错

也为了正常在头文件中定义常量,C++会将全局常量的连续性设为内部,这样在所有文件中都定义一次也不会引发多定义问题了

当然,全局变量的作用域是当前文件,要使用其他文件里的静态全局变量需要使用引用定义,即在定义前加上关键字extern表示该变量来自文件外

NOTE

值得注意的是,同一个static关键字在局部变量与全局变量中含义完全不同,在局部变量前加上static会将自动变量变为静态变量,而全局变量默认就是静态变量,在全局变量前加上static会将其默认的外部连接性改为内部连接性

C++11之前,auto关键字用于指出变量是自动变量,但只能用在默认是自动变量的变量上,并没有什么实际用处,因此,C++11将该操作符用作了自动类型推断

C语言中原先有一个register关键字,用于建议编译器使用CPU寄存器来存储此变量,在C++11中删除了这一功能,转而将原本auto的作用转移给register,但依然只能用于自动变量,故该关键字唯一的用处只是在全局变量和自动变量重名时提醒读代码的人这是自动变量

保留register的主要原因是,register原先的功能很好用(现在编译器可以自动完成这一优化了),使用非常广泛,轻易移除会导致以前的代码大规模报错,所以干脆给了一个没实际用处的功能了

4. 类型转换#

C++中基本数据类型之间可以转换,分为隐式转换和显式转换

  • 隐式转换

    在赋值时会自动进行所有支持的隐式转换

    在计算表达式式,若表达式两边不同,也会进行隐式转换,规则如下

    • 如果一个整型和浮点数操作,会将整型转换成浮点数
    • 如果两个不同长度整型或者两个不同长度浮点数操作,会将长度短的转换成长度长的
    • 如果不同长度的有符号和无符号数操作,若有符号数可以表示无符号数的所有可能,则转换为有符号数,否则转换为无符号数
    • bool型视为整型处理
  • 显式转换

    C语言中,使用(typename)操作符可以对变量进行显式转换,也叫强制转换,在C++中新增加了使用typename()这种类似函数调用的方法,其效果是一样的,显式转换可以处理一些不会进行隐式转换的场景,但这种操作往往存在较大风险,需谨慎使用

类型转换可能会产生很多问题:

  • 当较大整型转换成浮点数时,存在精度问题,而浮点数转换为整型时,小数部分会被直接舍去,大浮点数转换成整型时会直接转成类型支持的最大值
  • 当较长浮点数转换成较短浮点数时会产生精度损失,而较长整型转换成较短整型时,超出部分会被直接截断舍去
  • 当有符号数转换成无符号数时,会直接把补码当作无符号数处理
  • 当其他类型转换成布尔型时,只要不是全0都会被转换为true,反之,true会被转换为1false会被转换成0
C++基础三:变量、常量与复合数据类型
https://ssl.ztsubaki.xyz/posts/13/13/
作者
ZTsubaki
发布于
2025-03-01
许可协议
CC BY-SA 4.0