--- id: 语言元素 title: 语言元素 sidebar_position: 3 data: 2022年2月18日 --- ## 数据类型 ### 内置类型 C++ 为程序员提供了种类丰富的内置数据类型和用户自定义的数据类型。下表列出了七种基本的 C++ 数据类型: | 类型 | 关键字 | | -------- | ------- | | 布尔型 | bool | | 字符型 | char | | 整型 | int | | 浮点型 | float | | 双浮点型 | double | | 无类型 | void | | 宽字符型 | wchar_t | 一些基本类型可以使用一个或多个类型修饰符进行修饰: - signed - unsigned - short - long 下表显示了各种变量类型在内存中存储值时需要占用的内存,以及该类型的变量所能存储的最大值和最小值。 | 类型 | 位 | 范围 | | ------------------ | ------------- | ------------------------------------------------------- | | char | 1 个字节 | -128 到 127 或者 0 到 255 | | unsigned char | 1 个字节 | 0 到 255 | | signed char | 1 个字节 | -128 到 127 | | int | 4 个字节 | -2147483648 到 2147483647 | | unsigned int | 4 个字节 | 0 到 4294967295 | | signed int | 4 个字节 | -2147483648 到 2147483647 | | short int | 2 个字节 | -32768 到 32767 | | unsigned short int | 2 个字节 | 0 到 65,535 | | signed short int | 2 个字节 | -32768 到 32767 | | long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | | signed long int | 8 个字节 | -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807 | | unsigned long int | 8 个字节 | 0 to 18,446,744,073,709,551,615 | | float | 4 个字节 | +/- 3.4e +/- 38 (~7 个数字) | | double | 8 个字节 | +/- 1.7e +/- 308 (~15 个数字) | | long double | 8 个字节 | +/- 1.7e +/- 308 (~15 个数字) | | wchar_t | 2 或 4 个字节 | 1 个宽字符 | 从上表可得知,变量的大小会根据编译器和所使用的电脑而有所不同。下面实例会输出您电脑上各种数据类型的大小。 ```cpp #include using namespace std; int main() { cout << "Size of char : " << sizeof(char) << endl; cout << "Size of int : " << sizeof(int) << endl; cout << "Size of short int : " << sizeof(short int) << endl; cout << "Size of long int : " << sizeof(long int) << endl; cout << "Size of float : " << sizeof(float) << endl; cout << "Size of double : " << sizeof(double) << endl; cout << "Size of wchar_t : " << sizeof(wchar_t) << endl; return 0; } ``` ### typedef 声明 ### 枚举类型 枚举类型(enumeration)是C++中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。 创建枚举,需要使用关键字 **enum**。枚举类型的一般形式为: ```cpp enum enum-name { list of names } var-list; ``` 例如,下面的代码定义了一个颜色枚举,变量 c 的类型为 color。最后,c 被赋值为 "blue"。 ```cpp enum color { red, green, blue } c; c = blue; ``` 默认情况下,第一个名称的值为 0,第二个名称的值为 1,第三个名称的值为 2,以此类推。但是,您也可以给名称赋予一个特殊的值,只需要添加一个初始值即可。 例如,在下面的枚举中,**green** 的值为 5。 ```cpp enum color { red, green=5, blue }; ``` ## 变量类型 | 类型 | 描述 | | ------- | -------------------------------------------------- | | bool | 存储值 true 或 false。 | | char | 通常是一个八位字节(一个字节)。这是一个整数类型。 | | int | 对机器而言,整数的最自然的大小。 | | float | 单精度浮点值。 | | double | 双精度浮点值。 | | void | 表示类型的缺失。 | | wchar_t | 宽字符类型。 | C++ 也允许定义各种其他类型的变量,比如**枚举、指针、数组、引用、数据结构、类**等等。 ### 声明 变量声明向编译器保证变量以给定的类型和名称存在,这样编译器在不需要知道变量完整细节的情况下也能继续进一步的编译。变量声明只在编译时有它的意义,在程序连接时编译器需要实际的变量声明。 当您使用多个文件且只在其中一个文件中定义变量时(定义变量的文件在程序连接时是可用的),变量声明就显得非常有用。您可以使用 **extern** 关键字在任何地方声明一个变量。虽然您可以在 C++ 程序中多次声明一个变量,但变量只能在某个文件、函数或代码块中被定义一次。 ```cpp #include using namespace std; // 变量声明 extern int a, b; extern int c; extern float f; int main () { // 变量定义 int a, b; int c; float f; // 实际初始化 a = 10; b = 20; c = a + b; cout << c << endl; f = 70.0/3.0; cout << f << endl; return 0; } ``` ### 定义 变量定义就是告诉编译器在何处创建变量的存储,以及如何创建变量的存储。 ```cpp // 变量定义 // type variable_list; // 指定一个初始值 // type variable_name = value; // 示例 char ch; float f; int d = 3, f = 5; ``` ### 左值(Lvalues)和右值(Rvalues) C++ 中有两种类型的表达式: - **左值(lvalue):**指向内存位置的表达式被称为左值(lvalue)表达式。左值可以出现在赋值号的左边或右边。 - **右值(rvalue):**术语右值(rvalue)指的是存储在内存中某些地址的数值。右值是不能对其进行赋值的表达式,也就是说,右值可以出现在赋值号的右边,但不能出现在赋值号的左边。 变量是左值,因此可以出现在赋值号的左边。数值型的字面值是右值,因此不能被赋值,不能出现在赋值号的左边。下面是一个有效的语句: ```cpp int g = 20; ``` 但是下面这个就不是一个有效的语句,会生成编译时错误: ```cpp 10 = 20; ``` ## 变量作用域 用域是程序的一个区域,一般来说有三个地方可以声明变量: - 局部变量:在函数或一个代码块内部声明的变量。 - 形式参数:在函数参数的定义中声明的变量。 - 全局变量:在所有函数外部声明的变量。 ### 局部变量 在函数或一个代码块内部声明的变量,称为局部变量。 ```cpp #include using namespace std; int main (){ // 局部变量声明 int a, b; int c; // 实际初始化 a = 10; b = 20; c = a + b; cout << c; return 0; } // 输出 30 ``` ### 全局变量 在所有函数外部定义的变量(通常是在程序的头部),称为全局变量。全局变量的值在程序的整个生命周期内都是有效的。 全局变量可以被任何函数访问。也就是说,全局变量一旦声明,在整个程序中都是可用的。下面的实例使用了全局变量和局部变量: ```cpp #include using namespace std; // 全局变量声明 int g; int main () { // 局部变量声明 int a, b; // 实际初始化 a = 10; b = 20; g = a + b; cout << g; return 0; } // 输出 30 ``` 在程序中,局部变量和全局变量的名称可以相同,但是在函数内,**局部变量的值会覆盖全局变量的值。** ```cpp #include using namespace std; // 全局变量声明 int g = 20; int main (){ // 局部变量声明 int g = 10; cout << g; return 0; } // 输出 10 ``` ### 初始化局部变量和全局变量 当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。定义全局变量时,系统会自动初始化为下列值: | 数据类型 | 初始化默认值 | | -------- | ------------ | | int | 0 | | char | '\0' | | float | 0 | | double | 0 | | pointer | NULL | 正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果。 ## 常量 常量是固定值,在程序执行期间不会改变。这些固定的值,又叫做**字面量**。 常量可以是任何的基本数据类型,可分为整型数字、浮点数字、字符、字符串和布尔值。 常量就像是常规的变量,只不过常量的值在定义后不能进行修改。 ### 整数常量 整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数:0x 或 0X 表示十六进制,0 表示八进制,不带前缀则默认表示十进制。 整数常量也可以带一个后缀,后缀是 U 和 L 的组合,U 表示无符号整数(unsigned),L 表示长整数(long)。后缀可以是大写,也可以是小写,U 和 L 的顺序任意。 ```cpp 85 // 十进制 0213 // 八进制 0x4b // 十六进制 30 // 整数 30u // 无符号整数 30l // 长整数 30ul // 无符号长整数 ``` ### 浮点常量 浮点常量由整数部分、小数点、小数部分和指数部分组成。您可以使用小数形式或者指数形式来表示浮点常量。 当使用小数形式表示时,必须包含整数部分、小数部分,或同时包含两者。当使用指数形式表示时, 必须包含小数点、指数,或同时包含两者。带符号的指数是用 e 或 E 引入的。 ```cpp 3.14159 // 合法的 314159E-5L // 合法的 510E // 非法的:不完整的指数 210f // 非法的:没有小数或指数 .e55 // 非法的:缺少整数或分数 ``` ### 布尔常量 布尔常量共有两个,它们都是标准的 C++ 关键字: - **true** 值代表真。 - **false** 值代表假。 我们不应把 true 的值看成 1,把 false 的值看成 0。 ### 字符常量 字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L'x'),此时它必须存储在 **wchar_t** 类型的变量中。否则,它就是一个窄字符常量(例如 'x'),此时它可以存储在 **char** 类型的简单变量中。 字符常量可以是一个普通的字符(例如 'x')、一个转义序列(例如 '\t'),或一个通用的字符(例如 '\u02C0')。 在 C++ 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码: | 转义序列 | 含义 | | ---------- | -------------------------- | | \ | \ 字符 | | ' | ' 字符 | | " | " 字符 | | ? | ? 字符 | | \a | 警报铃声 | | \b | 退格键 | | \f | 换页符 | | \n | 换行符 | | \r | 回车 | | \t | 水平制表符 | | \v | 垂直制表符 | | \ooo | 一到三位的八进制数 | | \xhh . . . | 一个或多个数字的十六进制数 | ### 字符串常量 字符串字面值或常量是括在双引号 "" 中的。一个字符串包含类似于字符常量的字符:普通的字符、转义序列和通用的字符。 您可以使用空格做分隔符,把一个很长的字符串常量进行分行。 下面的实例显示了一些字符串常量。下面这三种形式所显示的字符串是相同的。 ```cpp "quot;hello, dear" "hello, \dear" "hello, " "d" "ear" ``` ### 定义常量 在 C++ 中,有两种简单的定义常量的方式: - 使用 **#define** 预处理器。 - 使用 **const** 关键字。 ### #define 预处理器 使用 #define 预处理器定义常量的形式: ```cpp #define identifier value ``` 实例: ```cpp #include using namespace std; #define LENGTH 10 #define WIDTH 5 #define NEWLINE '\n' int main() { int area; area = LENGTH * WIDTH; cout << area; cout << NEWLINE; return 0; } // 输出 50 ``` ### const 关键字 可以使用 **const** 前缀声明指定类型的常量,如下所示: ```cpp const type variable = value; ``` 具体请看下面的实例: ```cpp #include using namespace std; int main() { const int LENGTH = 10; const int WIDTH = 5; const char NEWLINE = '\n'; int area; area = LENGTH * WIDTH; cout << area; cout << NEWLINE; return 0; } // 输出 50 ``` *请注意,把常量定义为大写字母形式,是一个很好的编程实践。* ## 修饰符类型 C++ 允许在 **char、int 和 double** 数据类型前放置修饰符。修饰符用于改变基本类型的含义,所以它更能满足各种情境的需求。 下面列出了数据类型修饰符: - signed - unsigned - long - short 修饰符 **signed、unsigned、long 和 short** 可应用于整型,**signed** 和 **unsigned** 可应用于字符型,**long** 可应用于双精度型。 修饰符 **signed** 和 **unsigned** 也可以作为 **long** 或 **short** 修饰符的前缀。例如:**unsigned long int**。 C++ 允许使用速记符号来声明**无符号短整数**或**无符号长整数**。您可以不写 int,只写单词 **unsigned、short** 或 **unsigned、long**,int 是隐含的。例如,下面的两个语句都声明了无符号整型变量。 ```cpp unsigned x; unsigned int y; ``` 代码示例 ```cpp #include using namespace std; /* * 这个程序演示了有符号整数和无符号整数之间的差别 */ int main() { short int i; // 有符号短整数 short unsigned int j; // 无符号短整数 j = 50000; i = j; cout << i << " " << j; return 0; } // 输出 -15536 50000 ``` ### 类型限定符 类型限定符提供了变量的额外信息。 | 限定符 | 含义 | | -------- | ------------------------------------------------------------ | | const | **const** 类型的对象在程序执行期间不能被修改改变。 | | volatile | 修饰符 **volatile** 告诉编译器,变量的值可能以程序未明确指定的方式被改变。 | | restrict | 由 **restrict** 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。 | ## 存储类 存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。下面列出 C++ 程序中可用的存储类: - auto - register - static - extern - mutable - thread_local (C++11) 从 C++ 11 开始,auto 关键字不再是 C++ 存储类说明符,且 register 关键字被弃用。 ### auto 存储类 自 C++ 11 以来,**auto** 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。 C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在 C++17 中已删除这一用法。 根据初始化表达式自动推断被声明的变量的类型,如: ```cpp auto f=3.14; //double auto s("hello"); //const char* auto z = new auto(9); // int* auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型 ``` ### register 存储类 **register** 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。 ```cpp { register int miles; } ``` 寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。 ### static 存储类 **static** 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。 static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。 在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。 ```cpp #include // 函数声明 void func(void); static int count = 10; /* 全局变量 */ int main() { while(count--) { func(); } return 0; } // 函数定义 void func( void ) { static int i = 5; // 局部静态变量 i++; std::cout << "变量 i 为 " << i ; std::cout << " , 变量 count 为 " << count << std::endl; } /* 输出 变量 i 为 6 , 变量 count 为 9 变量 i 为 7 , 变量 count 为 8 变量 i 为 8 , 变量 count 为 7 变量 i 为 9 , 变量 count 为 6 变量 i 为 10 , 变量 count 为 5 变量 i 为 11 , 变量 count 为 4 变量 i 为 12 , 变量 count 为 3 变量 i 为 13 , 变量 count 为 2 变量 i 为 14 , 变量 count 为 1 变量 i 为 15 , 变量 count 为 0 */ ``` ### extern 存储类 **extern** 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 'extern' 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。 当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 *extern* 来得到已定义的变量或函数的引用。可以这么理解,*extern* 是用来在另一个文件中声明一个全局变量或函数。 extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示: ```cpp // main.cpp #include int count ; extern void write_extern(); int main() { count = 5; write_extern(); } // support.cpp #include extern int count; void write_extern(void) { std::cout << "Count is " << count << std::endl; } // shell $ g++ main.cpp support.cpp -o write $ ./write Count is 5 ``` ### mutable 存储类 **mutable** 说明符仅适用于类的对象,这将在本教程的最后进行讲解。它允许对象的成员替代常量。也就是说,mutable 成员可以通过 const 成员函数修改。 ### thread_local 存储类 使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。 thread_local 说明符可以与 static 或 extern 合并。 可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。 以下演示了可以被声明为 thread_local 的变量: ```cpp hread_local int x; // 命名空间下的全局变量 class X { static thread_local std::string s; // 类的static成员变量 }; static thread_local std::string X::s; // X::s 是需要定义的 void foo() { thread_local std::vector v; // 本地变量 } ``` ## 运算符 运算符是一种告诉编译器执行特定的数学或逻辑操作的符号。C++ 内置了丰富的运算符,并提供了以下类型的运算符: - 算术运算符 - 关系运算符 - 逻辑运算符 - 位运算符 - 赋值运算符 - 杂项运算符 ### 算术运算符 | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ---------------- | | + | 把两个操作数相加 | A + B 将得到 30 | | - | 从第一个操作数中减去第二个操作数 | A - B 将得到 -10 | | * | 把两个操作数相乘 | A * B 将得到 200 | | / | 分子除以分母 | B / A 将得到 2 | | % | 取模运算符,整除后的余数 | B % A 将得到 0 | | ++ | [自增运算符](https://edu.aliyun.com/cplusplus/cpp-increment-decrement-operators.html),整数值增加 1 | A++ 将得到 11 | | -- | [自减运算符](https://edu.aliyun.com/cplusplus/cpp-increment-decrement-operators.html),整数值减少 1 | A-- 将得到 9 | ### 关系运算符 | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ----------------- | | == | 检查两个操作数的值是否相等,如果相等则条件为真。 | (A == B) 不为真。 | | != | 检查两个操作数的值是否相等,如果不相等则条件为真。 | (A != B) 为真。 | | > | 检查左操作数的值是否大于右操作数的值,如果是则条件为真。 | (A > B) 不为真。 | | < | 检查左操作数的值是否小于右操作数的值,如果是则条件为真。 | (A < B) 为真。 | | >= | 检查左操作数的值是否大于或等于右操作数的值,如果是则条件为真。 | (A >= B) 不为真。 | | <= | 检查左操作数的值是否小于或等于右操作数的值,如果是则条件为真。 | (A <= B) 为真。 | ### 逻辑运算符 | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ----------------- | | && | 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 | (A && B) 为假。 | | \|\| | 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 | (A \|\| B) 为真。 | | ! | 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 | !(A && B) 为真。 | ### 位运算符 | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ------------------------------------------------------------ | | & | 如果同时存在于两个操作数中,二进制 AND 运算符复制一位到结果中。 | (A & B) 将得到 12,即为 0000 1100 | | \| | 如果存在于任一操作数中,二进制 OR 运算符复制一位到结果中。 | (A \| B) 将得到 61,即为 0011 1101 | | ^ | 如果存在于其中一个操作数中但不同时存在于两个操作数中,二进制异或运算符复制一位到结果中。 | (A ^ B) 将得到 49,即为 0011 0001 | | ~ | 二进制补码运算符是一元运算符,具有"翻转"位效果,即0变成1,1变成0。 | (~A ) 将得到 -61,即为 1100 0011,一个有符号二进制数的补码形式。 | | << | 二进制左移运算符。左操作数的值向左移动右操作数指定的位数。 | A << 2 将得到 240,即为 1111 0000 | | >> | 二进制右移运算符。左操作数的值向右移动右操作数指定的位数。 | A >> 2 将得到 15,即为 0000 1111 | ### 赋值运算符 | 运算符 | 描述 | 实例 | | ------ | ------------------------------------------------------------ | ------------------------------- | | = | 简单的赋值运算符,把右边操作数的值赋给左边操作数 | C = A + B 将把 A + B 的值赋给 C | | += | 加且赋值运算符,把右边操作数加上左边操作数的结果赋值给左边操作数 | C += A 相当于 C = C + A | | -= | 减且赋值运算符,把左边操作数减去右边操作数的结果赋值给左边操作数 | C -= A 相当于 C = C - A | | *= | 乘且赋值运算符,把右边操作数乘以左边操作数的结果赋值给左边操作数 | C *= A 相当于 C = C * A | | /= | 除且赋值运算符,把左边操作数除以右边操作数的结果赋值给左边操作数 | C /= A 相当于 C = C / A | | %= | 求模且赋值运算符,求两个操作数的模赋值给左边操作数 | C %= A 相当于 C = C % A | | <<= | 左移且赋值运算符 | C <<= 2 等同于 C = C << 2 | | >>= | 右移且赋值运算符 | C >>= 2 等同于 C = C >> 2 | | &= | 按位与且赋值运算符 | C &= 2 等同于 C = C & 2 | | ^= | 按位异或且赋值运算符 | C ^= 2 等同于 C = C ^ 2 | | \|= | 按位或且赋值运算符 | C \|= 2 等同于 C = C \| 2 | ### 杂项运算符 | 运算符 | 描述 | | -------------------- | ------------------------------------------------------------ | | sizeof | sizeof 运算符返回变量的大小。例如,sizeof(a) 将返回 4,其中 a 是整数。 | | Condition ? X : Y | 条件运算符。如果 Condition 为真 ? 则值为 X : 否则值为 Y。 | | , | 逗号运算符会顺序执行一系列运算。整个逗号表达式的值是以逗号分隔的列表中的最后一个表达式的值。 | | .(点)和 ->(箭头) | 成员运算符用于引用类、结构和共用体的成员。 | | Cast | 强制转换运算符把一种数据类型转换为另一种数据类型。例如,int(2.2000) 将返回 2。 | | & | 指针运算符 & 返回变量的地址。例如 &a; 将给出变量的实际地址。 | | * | 指针运算符\*指向一个变量。例如,*var; 将指向变量 var。 | ### 运算符优先级 | 类别 | 运算符 | 结合性 | | ---------- | --------------------------------- | -------- | | 后缀 | () [] -> . ++ - - | 从左到右 | | 一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 | | 乘除 | * / % | 从左到右 | | 加减 | + - | 从左到右 | | 移位 | << >> | 从左到右 | | 关系 | < <= > >= | 从左到右 | | 相等 | == != | 从左到右 | | 位与 AND | & | 从左到右 | | 位异或 XOR | ^ | 从左到右 | | 位或 OR | \| | 从左到右 | | 逻辑与 AND | && | 从左到右 | | 逻辑或 OR | \|\| | 从左到右 | | 条件 | ?: | 从右到左 | | 赋值 | = += -= *= /= %=>>= <<= &= ^= \|= | 从右到左 | | 逗号 | , | 从左到右 |