《C++ Primer(第5版)》学习笔记(第2章)

第2章 变量和基本类型

讲述内置类型,初步了解自定义数据结构。

2.1 基本内置类型

C++中定义了包括算术类型和空类型在内的基本数据类型。算术类型包括字符、整数、布尔值和浮点数。空类型不对应具体的值,只在特定场合使用,如函数不返回任何值时,使用空类型作为返回类型。

2.1.1 算术类型

类型含义最小尺寸
bool布尔类型未定义bool取值只有真或假两种。
char字符8位char的空间应确保可存放机器基本字符集中任意字符对应的数字值,即和机器字节一样大。
wchar_t宽字符16位用于确保可存放机器最大扩展字符集中的任一字符。
char16_tUnicode字符16位为Unicode字符集服务。
char32_tUnicode字符32位为Unicode字符集服务。
short短整型16位带符号。
int整型16位带符号,至少和short一样长。
C++是跨平台的语言,对于不同的系统,整型short、int、long和long long的长度不一样。
对一个系统,长度是固定的,如若short长度有16位,那么int也至少要有16位。
long长整型32位带符号,至少和int一样长。
long long长整型64位带符号,至少和long一样长。
float单精度浮点数6位有效数字通常以32位表示
double双精度浮点数10位有效数字通常以64位表示
long double扩展精度浮点数10位有效数字通常以96或128位表示

除去布尔型和扩展的字符型之外,其他整型可分为带符号的(signed)和无符号的(unsigned)两种。

在带符号的类型前加unsigned就可得到无符号类型。无符号类型中的所有比特都用来存储值,如8位的无符号类型可以表示0至255区间内的值。

带符号的类型被约定在表示范围内正值和负值的量应该平衡,如8位的带符号类型可以表示-127至127区间内的值,但大多数现代计算机将实际的表示范围定在-128至127。

字符型被分为三种:char、signed char、unsigned char,但字符的表现形式只有两种(带符号的和无符号的),类型char和类型signed char并不一样,char会表现为上述两种中的一种,具体哪种形式由编译器决定。

如何选择类型:

  1. 明确知晓数值不可能为负时选无符号类型。
  2. 使用int类型执行整数运算。若超过int的表示范围,选long long。
  3. 在算术表达式中不要使用char或bool,只有存放字符或布尔值时才使用它们。
  4. 执行浮点数运算时使用double,因为float通常精度不够,且双精度浮点数和单精度浮点数的计算代价相差无几,long double运行时消耗过大。

2.1.2 类型转换

类型1类型2类型1赋给类型2的结果
非布尔类型布尔类型初始值为0则结果为false,否则结果为true。
布尔类型非布尔类型初始值为false则结果为0,初始值为true则结果为1。
浮点类型整数类型结果值将仅保留浮点数中小数点前的部分。
整数类型浮点类型小数部分记为0,若整数所占空间超过了浮点类型容量,则精度有可能损失。
超出范围的值无符号类型初始值对无符号类型表示的数值总数取模后的余数。
如赋给8位大小的类型超出0至255区间的值,所得实际结果是该值对256取模后的余数。
超出范围的值带符号类型结果未定义,可能继续工作、崩溃或产生垃圾数据。
含有无符号类型的表达式

当一个算术表达式中既有无符号数又有int值时,int值就会转换成无符号数,转换过程相当于把int值直接赋给无符号变量。切勿混用带符号类型和无符号类型。

// 错误:变量i永远也不会小于0,循环条件一直成立
for (unsigned i = 10; i >= 0; --i)
	std::cout << i << std::endl;

// 正确,先给i赋值比要输出的数大1的数
unsigned i = 11;
while (i > 0){
	--i;
	std::cout << i << std::endl;
}

2.1.3 字面值常量

整型和浮点型字面值

整型字面值可写作十进制、八进制(0开头)、十六进制(0x或0X开头)。浮点型字面值表现为一个小数或以科学计数法表示的指数,其中指数部分用E或e标识,浮点型字面值默认是一个double,可以使用后缀来表示其他浮点型。

字符和字符串字面值

由单括号括起来的一个字符是字符字面值,如'a'

由双引号括起来的零个或多个字符构成字符串字面值,如"Hello World!"。字符串字面值的类型实际上是由常量字符构成的数组,编译器在每个字符串的结尾处添加一个空字符'\0',因此字符串字面值的实际长度要比它的内容多1。若两个字符串字面值位置紧邻且仅由空格、缩进和换行符分隔,则它们实际上是一个整体。

转义字符

有两类字符程序员不能直接使用,一类是不可打印的字符如退格或其他控制字符,因为它们没有可视图符,另一类是在C++语言中有特殊含义的字符如单引号、双引号、问号等。在这种情况下需要使用转义序列。C++规定的转义序列包括:

换行符横向制表符报警(响铃)符纵向制表符
\n\t\a\v
退格符双引号反斜线问号
\b\"\\\?
单引号回车符进纸符
\'\r\f

也可使用泛化的转义序列,其形式是'\x'后紧跟1个或多个十六进制数字,或者'\'后紧跟1个、2个或3个八进制数字,其中数字部分表示的是字符对应的数值。

指定字面值的类型
字符和字符串字面值
前缀含义类型
uUnicode16字符char16_t
UUnicode32字符char32_t
L宽字符wchar_t
u8UTF-8(仅用于字符串字面常量)char
后缀最小匹配类型*
整型字面值u or Uunsigned
l or Llong
ll or LLlong long

*整型字面值能分别指出它是否带符号以及占用多少空间。若后缀中有U,则该字面值属于无符号类型,从unsigned int、unsigned long、unsigned long long中选择能匹配的空间最小的一个作为其数据类型。若后缀中有L,则字面值至少是long。
可将U与L或LL合在一起使用,如以UL为后缀的字面值的数据类型将根据具体数值情况或者取unsigned long,或者取unsigned long long。

后缀类型
浮点型字面值f or Ffloat
l or Llong double
布尔字面值和指针字面值

布尔字面值:trueflase
指针字面值:nullptr

2.2 变量

对C++来说,变量(valuable)和对象(object)一般可以互换使用。对象是具有某种数据类型的内存空间。

2.2.1 变量定义

变量名的类型由类型说明符指定,其中变量名以逗号分隔,以分号结束。

// 库类型定义,string是在命名空间std中定义的
std::string book("0-201-78345-X");
初始值

当对象在创建时获得了一个特定的值,说这个对象被初始化了。初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代。

列表初始化
// 以下四条语句都能实现定义一个名为x的int变量并初始化为0
int x = 0;
int x = {0};
int x{0};
int x(0);

无论是初始化对象还是某些时候为对象赋新值,都可以使用一组由花括号括起来的初始值了。如果使用列表初始化且初始值存在丢失信息的风险,则编译器将报错。

long double pi = 3.1415926536;
int a{pi}, b = {pi}; // 错误:转换未执行,因为存在丢失信息的风险
int c(pi), c = pi;   // 正确:转换执行,且丢失了部分值
默认初始化

如果定义变量时没有指定初始值,则变量会被默认初始化。默认值由变量类型决定,同时定义变量的位置也会对此有影响。

如内置类型的变量未被显式初始化,则定义于任何函数体之外的变量被初始化为0,定义在函数体内部的内置类型变量将不被初始化。未被初始化的内置类型变量的值时未定的,若试图拷贝或以其他形式访问此类值将引发错误。

每个类各自决定其初始化对象的方式,是否允许不经初始化就定义对象也由类自己决定,绝大多数类都支持无须显式初始化而定义对象,这样的类提供了一个合适的默认值。

2.2.2 变量声明和定义的关系

C++支持分离式编译,即允许将程序分割为若干个文件,每个文件可被独立编译,此时必须将声明和定义区分开来。变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。变量能且只能被定义一次,但是声明可以被多次声明。

extern int i;              // 声明i而非定义i
int j;                     // 声明并定义j
extern double pi = 3.1416; // 定义,extern若包含初始值就变成了定义

2.2.3 标识符

C++的标识符由字母、数字和下划线组成,其中必须以字母或下划线开头。标识符长度无限制,但区分大小写。C++保留了一些关键字供语言本身使用,这些关键字不能被用作标识符,同时也为标准库保留了一些名字。用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。定义在函数体外的标识符不能以下划线开头。

约定俗成的变量命名规范:

  1. 标识符要体现实际含义。
  2. 变量名一般用小写字母。
  3. 用户自定义类名一般以大写字母开头。
  4. 由多个单词组成的标识符应有所区分,如单词间添加下划线。

2.2.4 名字的作用域

名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束,C++中大多数作用域都以花括号分隔。全局作用域内的名字在整个程序的范围内都可使用。

嵌套的作用域

作用域能彼此包含。作用域中声明了的某个名字,它所嵌套的所有作用域中都能访问该名字,同时允许在内层作用域中重新定义外层作用域已有的名字。若想在内层作用域中使用全局变量,需在全局变量前加作用域操作符::。若函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。

2.3 复合类型

复合类型是指基于其他类型定义的类型,接下来主要介绍两种:引用指针

一条声明语句由一个基本数据类型和紧随其后的一个声明符列表组成。目前接触的声明语句中,声明符就是变量名,变量的类型就是声明的基本数据类型。

2.3.1 引用

C++11新增了一种“右值引用”,这种引用主要用于内置类,将在后续做详细介绍。严格来说术语“引用”指的是“左值引用”。

引用为对象起了另外一个名字,引用类型引用另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d是声明的变量名:

int ival = 1024;
int &refVal = ival; // refVal指向ival(是ival的另一个名字)
int &refVal2;       // 报错:引用必须被初始化

一般初始化变量时,初始值会被拷贝到新建的对象中,但定义引用时程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。一旦初始化完成,引用将和它的初始值对象一直绑定在一起,因为无法令引用重新绑定到另外一个对象,因此引用必须初始化。

引用并非对象,他只是为一个已经存在的对象所起的另外一个名字,因此也不能定义引用的引用。

定义了一个引用后,对其进行的所有操作都是在与之绑定的对象上进行的。

引用的定义

允许在一条语句中定义多个引用,其中每个引用标识符都必须以符号&开头。

引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。

int &refVal4 = 10;   // 错误:引用类型的初始值必须是一个对象
double dval = 3.14;
int &refVal5 = dval; // 错误:此处引用类型的初始值必须是int型对象

2.3.2 指针

指针是指向另外一种类型的复合类型,与引用类似,指针也实现了对其他对象的间接访问。不同点:

引用指针
已存在对象的别名本身就是对象,允许赋值和拷贝
必须且只能在定义时被初始化一次,之后不可变无须在定义时赋初始值,在指针的生命周期内可先后指向几个不同的对象
不能为空可以为空
“sizeof 引用”得到的是所指向对象的大小“sizeof 指针”得到的是指针本身(所指向的变量或对象的地址)的大小
没有const有const,const的指针不变
自增操作直接反应到所指向的对象,而不是改变指向自增操作会使指针指向下一个对象,而不是改变所指对象的内容

使用*d定义指针类型,其中d是变量名,如果一条语句定义了几个指针变量,每个变量前都必须有*。指针存放某个对象的地址,获取该地址需要使用取地址符&

int *ip1, *ip2;     // ip1和ip2都是指向int型对象的指针
double dval, *dp2;  // dp2是指向double型对象的指针
int ival = 42;
int *p = &ival;     // p存放变量ival的地址,或者说p是指向变量ival的指针

double *pd = &dval; // 正确:初始值是double型对象的地址
double *pd2 = pd;   // 正确: 初始值是指向double对象的指针(也是对象)
int *pi = pd;       // 错误:指针pi的类型和pd的类型不匹配
pi = &dval;         // 错误:试图把double型对象的地址赋给int型指针
// 除了2.4.2和15.2.3节将要介绍的两种例外情况,其他所有指针的类型都要和它所指向的对象严格匹配

指针有以下四种状态:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置。
  3. 空指针。
  4. 无效指针。

试图拷贝或访问无效指针将引发错误,第2、3种没有指向任何具体对象,所以试图访问此类指针对象是不允许的。

利用指针访问对象

若指针指向了一个对象,则可使用*来访问该对象,给*p赋值实际上是给指针所指的对象赋值。

空指针

空指针不指向任何对象,初始化空指针:

int *p1 = nullptr; // 等价于int *p1 = 0;
int *p2 = 0;       // 直接将p2初始化为字面常量
// 需要先#include <cstdlib>
int *p3 = NULL;    // 等价于int *p3 = 0;
// 不能把int变量直接赋给指针,即使int变量恰好等于0也不行

建议初始化所有指针,并且尽量等定义了对象以后再定义指向它的指针。

赋值和指针

如何搞清楚一条赋值语句到底是改变了指针的值还是改变了指针所指的对象的值:赋值永远改变的是等号左侧的对象。

pi = &ival; // pi的值被改变,现在pi指向了ival
*pi = 0;    // ival的值被改变,指针pi并没有改变
其他指针操作

只要指针拥有一个合法值,就能将其用在条件表达式中,如果指针的值是0(空指针),条件取false,其他非0指针的条件值是true。

两个合法指针可用==!=来比较,两个指针存放的地址相同,则它们相等,否则不相等。有以下几种可能:1. 都为空;2. 都指向同一个对象;3. 都指向了同一个对象的下一个地址;4. 一个指针指向某对象,另一个指向另外对象的下一个地址。

void* 指针

void是一种特殊的指针类型,可用于存放任意对象的地址。可用void指针:和别的指针比较、作为函数的输入或输出,或者赋给另外的一个void指针。不能直接操作void指针所指的对象。

2.3.3 理解复合类型的声明

指向指针的指针

指针是内存中的对象,像其他对象一样有自己的地址,因此允许把指针的地址再存放到另一个指针中。

int ival = 1024;
int *pi = &ival; // pi指向一个int型的数
int **ppi = &pi; // ppi指向一个int型的指针
指向指针的引用

引用不是对象,因此不能定义指向引用的指针,但可定义指针的引用

int i = 42;
int *p;      // p是int型指针
int *&r = p; // r是对指针p的引用

r = &i;      // r引用了一个指针,因此给r赋值&i就是令p指向i
*r = 0;      // 解引用r(相当于解引用指针p)得到i,也就是p所指对象,将i改为0

如何理解 r 的类型:从右往左阅读 r 的定义,离变量名最近的符号(此例中是 &r 的符号 &),因此 r 是一个引用。声明符中其余部分用于确定 r 引用的类型是什么,此例中的符号 * 说明 r 引用是一个指针。最后声明的基本数据类型指出 r 引用的是一个 int 指针。

2.4 const限定符

const int bufSize = 512; // 把缓冲区定义成常量

const对象一旦创建后其值就不能再改变,任何试图改变const对象的行为都将引发错误,所以const对象必须初始化。

默认状态下,const对象仅在文件内有效,在每个用了const对象的文件都必须对它定义,编译器在编译过程中把用到const变量的地方都替换成对应的初始值。

某些时候const变量的初始值不是一个常量表达式,但又确实有必要在文件间共享,此种情况下,我们不希望编译器为每个文件分别生成独立的变量,而是希望只在一个文件中定义const,而在其他多个文件中声明并使用它。解决办法是对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次:

// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h头文件
extern const int bufSize; // 与file_1.cc中定义的bufSize是同一个,extern指明bufSize并非本文件所独有,它的定义将在别处出现

2.4.1 const的引用

对const的引用称为常量引用,对常量的引用不能被用作修改它所绑定的对象。

const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对象都是常量
r1 = 42;            // 错误:r1是对常量的引用
int &r2 = ci;       // 错误:非常量引用不能指向一个常量对象
初始化和对const的引用

2.3.1中提到引用的类型必须与其所引用的对象的类型一致,但有两种例外,其中一种就是初始化常量引用时允许用任意表达式作为初始值,只要该表达式能转换成引用的类型即可。允许一个常量引用绑定非常量的对象、字面值,甚至是一个表达式:

int i = 42;
const int &r1 = i;
const int &r2 = 42;     // 正确:常量引用可绑定字面值
const int &r3 = r1 * 2; // 正确:常量引用可绑定表达式

当一个常量引用被绑定到另外一种类型上时:

double dval = 3.14;
const int &ri = dval; 

编译器此时将上述代码转换成了如下形式:

const int temp = dval; // 由双精度浮点数生成一个临时的整型变量
const int &ri = temp;  // 让ri绑定这个临时量

此时如果ri不是常量,则允许对ri赋值,即改变ri引用对象的值,而此时ri绑定的对象实际是一个临时变量,并非变量dval,因此,对ri赋值并未起到改变dval的作用。

常量引用可能引用一个非const的对象,不允许通过常量引用修改引用对象(非const)的值,但可以通过其他途径修改引用对象的值。

2.4.2 指针和const

可令指针指向常量或非常量,指向常量的指针不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针。

2.3.2中提到指针的类型必须与其所指对象的类型一致,但有两种例外,第一种就是允许令一个指向常量的指针指向一个非常量对象。不能通过指向常量的指针改变对象的值,但对象的值可以通过其他途径改变

所谓指向常量的指针或常量引用,并不一定所指的对象就是常量。

const指针

指针是对象而引用不是,因此允许把指针本身定义为常量,常量指针必须初始化,一旦初始化完成,则它的值(即存放在指针中的那个地址)就不能再改变了。

int errNumb = 0;
int *const curErr = &errNumb;
// 从右往左,curErr首先是一个常量对象,然后*说明curErr是一个常量指针,指向一个int对象
const double pi = 3.14159;
const double *const pip = &pi;
// 从右往左,pip首先是一个常量对象,*说明pip是一个常量指针,指向一个const double对象

是否能通过指针修改其所指对象的值取决于所指对象的类型,与指针本身的类型无关,如可用指针curErr修改errNumb的值。

2.4.3 顶层const

顶层const:表示任意的对象本身是常量,对任何数据类型都适用。
底层const:只讨论指针或引用等复合类型,表示指针所指的对象是一个常量。

对于一般变量类型来说,没有顶层const和底层const的区别,只有指针或引用这类复合类型变量,才有区别。

指针本身添加const限定符就是顶层const,而指针所指的对象添加const限定符就是底层const。

int i = 0;
int *const p1 = &i;       // 不能改变p1的值,顶层const
const int ci = 42;        // 不能改变ci的值,顶层const
const int *p2 = &ci;      // 允许改变p2的值,底层const
const int *const p3 = p2; // 左边底层const,右边顶层const
const int &r = ci;        // 底层const(声明引用的都是底层const)

执行对象的拷贝操作时,顶层const不受影响。

但底层const却有限制,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能转换。一般非常量能转换成常量,反之则不行。

int *p = p3;       // 错误:p3包含底层const的定义,而p没有,要想存放常量对象的地址,只能使用指向常量的指针
p2 = p3;           // 正确:p2、p3都是底层const
p2 = &i;           // 正确:int*能转换成const int*
int &r = ci;       // 错误:普通的int&不能绑定到int常量上
const int &r2 = i; // 正确:const int&可以绑定到普通int上

2.4.4 constexpr和常量表达式

值不会改变且在编译过程就能得到计算结果的表达式称为常量表达式。

一般来说如果认定变量是一个常量表达式,那就把它声明成constexpr类型,声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制,称这些类型为“字面值类型”。算术类型、引用和指针都属于字面值类型,自定义类则不属于字面值类型,即不能被定义成constexpr。其他字面值类型将在7.5.6和19.3介绍。

  • 指针和引用定义成constexpr的初始值收到严格限制,一个constexpr指针的初始值必须是nullptr或0,或是存储于某个固定地址中的对象。
  • 6.1.1中:函数体内定义的变量一般不存放在固定地址,因此constexpr指针不能指向这样的变量,定义于所有函数体之外的对象地址固定不变,能用来初始化constexpr指针。
  • 6.1.1中:允许函数定义一类有效范围超出函数本身的变量,这类变量也有固定地址,允许constexpr引用和指针绑定到这样的变量。
指针和constexpr

如果在constexpr声明中定义了一个指针,constexpr仅对指针有效,与指针所指的对象无关。constexpr指针既可以指向常量也可以指向一个非常量。

2.5 处理类型

2.5.1 类型别名

typedef "类型1" "别名1";
using "别名2" = "类型2";

类型别名和类型的名字等价,只要是类型的名字能出现的地方就能使用类型别名。

指针、常量和类型别名
typedef char *pstring;  // 相当于typedef char* pstring;
const pstring cstr = 0; // cstr是指向char的常量指针
const pstring *ps;      // ps是一个指针的指针,对象是指向char的常量指针

2.5.2 auto类型说明符

auto类型说明符让编译器通过初始值来推算变量的类型,auto定义的变量必须有初始值。可用auto在一条语句中声明多个变量,但一条声明语句只能有一个基本类型,所以该语句中所有变量的初始基本数据类型必须一致。

2.5.3 decltype类型指示符

decltype的作用是选择并返回操作数的数据类型,编译器分析表达式并得到它的类型,却不实际计算表达式的值。

decltype(f()) sum = x; // 使用当调用发生时f的返回值类型作为sum的类型

如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内,引用从来都是作为其所指对象的同义词出现,只有用在decltype处是一个例外)。

decltype和引用

decltype((variable))的结果永远是引用,而decltype(variable)的结果只有当variable本身就是一个引用时才是引用。

int i = 42, *p = &i, &r = i;
decltype(r + 0) b; // 正确:decltype(r)的结果是引用类型,而加法结果是int,因此b是int
decltype(*p) c;    // 错误:c是int&,必须初始化
decltype((i)) d;   // 错误:d是int&,必须初始化
decltype(i) e;     // 正确:e是int
auto与decltype的区别
  • decltype在处理顶层const和引用的方式与auto有些许不同,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。而auto一般会忽略掉顶层const,底层const则会保留下来。
const int ci = 42, &cj = ci;
decltype(ci) x = 0;   // x类型是const int
auto a = ci;          // a类型是int,ci的顶层const特性被忽略掉了
decltype(cj) y = x;   // y类型是const int&
auto b = cj;          // b类型是int,cj是ci的别名,ci是顶层const
auto c = &ci;         // c类型是一个指向整数常量的指针(对常量对象取地址是一种底层const)
  • decltype的结果类型与表达式形式密切相关,一层括号,得到的类型就是该变量的类型。双层括号,则是引用类型。
  • auto让编译器通过初始值推断其类型,而decltype虽也让编译器分析表达式,但并不计算表达式的值。

2.6 自定义数据结构

struct定义数据结构:

struct Sales_data {          // 类名Sales_data
	std::string bookNo;      // 类数据成员bookNo,名字必须唯一,可与类外部定义的名字重复
	unsigned units_sold = 0; // 初始化数据成员,未初始化的成员将被默认初始化
	double revenue = 0.0;
};
// 定义对象
Sales_data accum, trans, *salesptr;

可用对象名.类数据成员名来访问并使用类对象的数据成员。

2.6.3 编写自己的头文件

类一般都不定义在函数体内,通常被定义在头文件中,且类所在的头文件名字应与类名一样。头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明。

预处理器概述

头文件通常包含那些只能被定义一次的实体,如类、const变量,同时也经常用到其他头文件的功能,在书写头文件时需要做适当处理,使其遇到多次包含的情况也能安全和正常地工作。

预处理器是在编译之前执行的一段程序,如当预处理器看到#include标记就会用指定的头文件的内容替代#include

还有一项预处理功能是头文件保护符,头文件保护符依赖于预处理变量,预处理变量有两种状态:已定义和未定义。

  • #define指令:把一个名字设定为预处理变量。
  • #ifdef指令:当且仅当变量已定义时为真
  • ifndef指令:当且仅当变量未定义时为真
  • #endif指令:一旦检查结果为真,则执行后续操作直到遇到#endif指令为止

使用这些功能能有效地防止重复包含的发生:

#ifndef SALES_DATA_H
// 检查结果为真,预处理器顺序执行后续操作直到遇到#endif
#define SALES_DATA_H
#include <string>
struct Sales_data {
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
#endif

/* 
 * 预处理变量SALES_DATA_H变为已定义,Sales_data.h也会拷贝到程序中来
 * 后续若再一次包含Sales_data.h,则#ifndef检查结果为假,编译器将忽略#ifndef到#endif间的内容
 */

头文件即使目前还没有被包含在其他任何头文件中,也应该设置保护符,没必要在意程序到底需不需要,加上就行。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值