参考资料:C++ Primer 中文版(第5版)——[美] Stanley B. Lippman [美] Josée Lajoie [美] Barbara E. Moo 著 王刚 杨巨峰 译

代码编辑器:VS 2019

文章目录

  • 1. 基本内置类型
    • 1.1 算术类型
    • 1.2 类型转换
    • 1.3 字面值常量
      • 1.3.1 整型和浮点型字面值
      • 1.3.2 字符和字符串字面值
      • 1.3.3 布尔字面值和指针字面值
  • 2. 变量
    • 2.1 变量的定义
      • 2.1.1 列表初始化
      • 2.1.2 默认初始化
    • 2.2 名字的作用域
  • 3. 复合类型
    • 3.1 引用
    • 3.2 指针
  • 4. const 限定符
    • 4.1 const 的引用
    • 4.2 指针和 const
    • 4.3 顶层 const
    • 4.4 constexpr 和常量表达式
      • 4.4.1 常量表达式
      • 4.4.2 constexpr 变量
      • 4.4.3 字面值类型
      • 4.4.4 指针和 constexpr
  • 5. 处理类型
    • 5.1 类型别名
    • 5.2 auto 类型说明符
    • 5.3 decltype 类型指示符
  • 6. 自定义数据类型

1. 基本内置类型

C++ 定义了一套包括算术类型(arithmetic type)和空类型(void)在内的基本数据类型。其中,算术类型包含了字符整型数布尔值浮点数

1.1 算术类型

其中常用的那些类型(intcharfloatdoublelongshortlong long等)和 C 中的一样,不再赘述。布尔类型是 C 中没有的,C++ 中的布尔类型的取值为truefalse

1.2 类型转换

类型转换:

  • 算术值 0 赋给布尔类型时,结果为false,否则结果为true
  • 布尔值true赋给非布尔类型,结果为 0 ,false则结果为 1。
  • 浮点数赋给整数,结果仅保留浮点数中小数点之前的部分。
  • 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后所得的余数。
  • 当我们赋给带符号类型一个超出它表示范围的值时,结果是未定义的(undefined)。此时,程序可能继续工作、可能崩溃,也可能生成垃圾数据。

1.3 字面值常量

一个形如 42 的值被称作字面值常量(literal),这样的值一望而知。每个字面值都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。

1.3.1 整型和浮点型字面值

整型字面值可以写作十进制数、八进制数和十六进制数的形式。

类型 表示形式
十进制 常规表示方法
八进制 以 0 开头
十六进制 以 0x 或 0X 开头

默认情况下,十进制字面值是带符号数,八进制和十六进制字面值既可能是带符号的也可能是无符号的。

尽管整型字面值可以存储在带符号数据类型中,但严格来说,十进制字面值不会是负数。负号仅仅是对字面值取负而已。

浮点型字面值默认是double型的,可以以小数形式表示,也可以用指数形式表示。

1.3.2 字符和字符串字面值

由单引号括起来的一个字符称为char型字面值,双引号括起来的零个或多个字符串构成字符串型字面值。

注:一个字符也包括若干转义字符。

1.3.3 布尔字面值和指针字面值

truefalse是布尔类型的字面值。

nullptr是指针字面值,代表空指针。

2. 变量

2.1 变量的定义

变量的声明不再赘述,下面重点放在初始化上。

2.1.1 列表初始化

C++ 语言定义了初始化的好几种不同形式,这也是初始化问题复杂性的一个体现。例如:

//我们想要给 int 型变量 a 赋初值为2,以下的4条语句都可以实现:
int a = 2;
int a = {2};
int a{2};
int a(2);

用花括号来初始化变量即为列表初始化。当用于内置类型的变量时,这种初始化有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将会报错,比如:

long double pi = 3.1415926535;
int a{pi};//报错,因为存在丢失信息的风险
int b(pi), c = pi;//正确,执行类型转换,且确实丢失了部分值

2.1.2 默认初始化

如果定义变量时没有指定初值,则变量被默认初始化,此时变量被赋予了”默认值“。默认值到底是什么由变量类型决定,同时定义变量的位置也会对此有影响。

每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类自己决定。如果类要求每个对象都必须显式初始化,此时创建对象不做初始化操作将引发错误。

2.2 名字的作用域

不论是在程序的什么位置,使用到的每个名字都会指向一个特定的实体:变量、函数、类型等。然而,同一个名字如果出现在程序的不同位置,也可能指向的是不同实体。

作用域(scope)是程序的一部分,在其中名字有其特定的含义。C++ 语言中大多数作用域都以花括号分隔。

建议:当你第一次使用变量时再定义它。这样做有助于更容易地找到变量地定义。更重要的是,当变量的定义离它第一次被使用的地方很近时,我们也会赋给它一个比较合理的初始值。

如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。

3. 复合类型

复合类型(compound type)是指基于其他类型定义的类型。C++ 语言有几种复合类型,这里介绍其中的两种:引用指针

除了变量名组成的比较简单的声明符外,还有更复杂的声明符,它基于基本数据类型得到更复杂的类型,并把它指定给变量。

3.1 引用

C++11 中新增加一种引用:所谓的"右值引用(rvalue reference)",这种引用主要用于内置类。
严格来说,当我们使用术语"引用(reference)"时,指的其实是"左值引用(lvalue reference)"。

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

int num = 10;
int &renum = num;   // renum 指向 num (是 num 的另一个名字)
int &renum2;        // 报错:引用必须被初始化

一般在初始化变量时,初始值会被拷贝到新建的对象中。

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

定义了一个引用之后,对其进行的所有操作(赋值,获取值,作为另一个变量的初始值等等)都是在与之绑定的对象上进行的。

因为引用本身不是一个对象,所以不能定义引用的引用

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

除了两种例外情况,其他所有引用的类型都要和与之绑定的对象严格匹配。两种例外情况是:

  • 第一种例外情况(跳转至例子):在初始化常量引用时允许用任意表达式作为初始值,只有该表达式的结果能够转换成引用的类型即可。
  • 第二种例外情况:对于存在继承关系的类,我们可以将基类的指针或引用绑定到派生类对象上。这就有一层极为重要的含义:当使用基类的引用(或指针)时,实际上我们并不清楚该引用(或指针)所绑定对象的真实类型。该对象可能是基类的对象,也可能是派生类的对象
int &renum = 10;    //错误:引用类型的初始值必须是一个对象
double dnum = 3.14;
int &renum2 = dnum;    //错误:此处引用类型的初始值必须是 int 型对象

3.2 指针

指针(pointer)是“指向(point to)”另外一种类型的复合类型。与引用类似,指针也实现了对其他对象的间接访问。然而,指针与引用相比又有很多不同点:

  • 其一,指针本身就是一个对象(引用本身并不是对象),允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
  • 其二,指针无须在定义时赋初值。定义指针时如果没有被初始化,它也拥有一个不确定的值。

除了两种例外情况外,其他所有指针的类型都要和它所指向的对象严格匹配。两种例外情况:

  • 第一种例外情况:允许令一个指向常量的指针指向一个非常量对象。
  • 第二种例外情况:同上面引用的第二种例外情况。点我跳转

指针的值应属于下列4种状态之一:

  1. 指向一个对象。
  2. 指向紧邻对象所占空间的下一个位置。
  3. 空指针,意味着指针没有指向任何对象。
  4. 无效指针,也就是上述情况之外的其他值。

试图拷贝或以其他方式访问无效指针都将引发错误。编译器并不负责检查此类错误,这一点和试图使用未经初始化的变量是一样的。访问无效指针的后果无法预计,因此程序员必须清楚给定的指针是否有效。

空指针的几种生成方法:

int *pi = nullptr;      //等价于 int *p1 = 0;
int *p2 = 0;           //直接将 p2 初始化为字面常量0//在有 #include cstdlib 的情况下,可以使用下面的语句:
int *p3 = NULL;            //等价于 int *p3 = 0;

int 变量不能直接赋给指针!即使 int 变量的值恰好等于 0 也不行!

建议:初始化所有指针。

任何非零指针对应的条件值都是true

void *是一种特殊的指针类型,可用于存放任意对象的地址。一个void*指针存放着一个地址,这一点和其他指针类似。不同的是,我们对该地址中到底是个什么类型的对象并不了解:

void *pv = &obj;    //obj 可以是任意类型的对象

利用void *指针能做的事比较有限:拿它和别的指针比较、作为函数的输入和输出,或者赋给另外一个void *指针。不能直接操作void *指针所指的对象,因为我们并不知道这个对象到底是什么类型,也就无法确定能在这个对象上做哪些操作。

引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用:

int i = 1;
int *p;
int *&r = p;   //r 是一个对指针 p 的引用r = &i;            //r 引用了一个指针,因此给 r 赋值 &i 就是令 p 指向 i
*r = 0;            //解引用 r 得到 i,也就是 p 指向的对象,将 i 的值改为0

4. const 限定符

有时我们希望定义这样的一种变量,它的值不能被改变。例如用一个变量来表示缓冲区的大小。使用变量的好处是当我们觉得缓冲区大小不再合适时,很容易对其进行调整。另一方面也应随时警惕防止程序一不小心改变了这个值,为了满足这一要求,可以用关键字const对变量的类型加以限定。

const对象一旦创建后其值就不能再改变,所以const对象必须初始化。初始值可以是任意复杂的表达式。

利用一个对象去初始化一个const对象时,前者是不是const类型都无关紧要。

默认状态下,const对象仅在文件内有效,要想在文件间共享,需要使用extern关键字。为了方便,我们对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了,即在一个文件中定义const,而在其他多个文件中声明并使用它。

4.1 const 的引用

可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象

前面提到引用的两个例外,这里便出现了第一种例外的情况。在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。引用的第一种例外的例子:

  //例子int i = 1;const int &r1 = i;const int &r2 = 1;       //正确:r1是一个常量引用const int &r3 = r1 * 2;  //正确:r3是一个常量引用const int &r4 = r1 * 2;  //错误:r4是一个普通的非常量引用

要弄清楚这种例外发生的原因,就要弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:

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

上面这两个语句是可以执行的,而且输出ri引用的值发现等于3。发生了什么呢?事实上,为了确保让ri绑定一个整数,编译器把上述代码变成如下形式:

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

在这种情况下,ri绑定了一个临时量(temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建了一个未命名的对象。C++ 程序员们常把临时量对象简称为临时量。

如果把语句改为:

double dval = 3.14;
int &ri = dval;        //错误:引用的类型与要绑定的对象的类型不一致

很容易想明白它非法的原因,因为这样会让ri绑定到一个临时量对象上,这样对ri进行赋值操作就没有任何意义了,所以C++将其归为非法。

4.2 指针和 const

与引用一样,也可以令指针指向常量或非常量。类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针。

前面提到,指针的类型必须与其所指对象的类型一致,但是有两个例外。第一种例外情况是允许令一个指向常量的指针指向一个非常量对象:

double dval = 3.14;
const double *cptr = &dval;        //正确:但是不能通过 cptr 改变 dval 的值

下面介绍一下 const 指针。

指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,它的值就不能再改变了(即它指的那个地址就不能再改变了)。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值:

int num = 0;
int *const cpnum = #       //cpnum 将一直指向 num
const double pi = 3.14159;
const double *const pip = π //pip 是一个指向常量对象的常量指针
/*
弄清楚这些声明的含义最行之有效的办法是从右向左阅读。
如第2行,int *const cpnum
const 代表 cpnum 本身是一个常量对象,对象的类型由声明符的其余部分确定, * 即代表是个指针,所以 cpnum 是一个常量指针,int 则代表常量指针对象指向的是一个 int 对象。第4行的类似。
*/

4.3 顶层 const

如前所述,指针本身是一个对象,它又可以指向另外一个对象,因此指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层 const(top-level const)表示指针本身是个常量,而用名词底层 const(low-level const)表示指针所指的对象是一个常量。

更一般的,顶层 const 可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层 const 则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层 const 也可以是底层 const ,这一点和其他类型相比区别明显:

int i = 0;
int *const p1 = &i;        //不能改变 p1 的值,这是一个顶层 const
const int ci = 1;      //不能改变 ci 的值,这是一个顶层 const
const int *p2 = &ci;   //允许改变 p2 的值,这是一个底层 const
const int *const p3 = p2;//靠右的const是顶层 const,靠左的是底层const
const int &r = ci;     //用于声明引用的 const 都是底层 const

当执行对象的拷贝操作时,常量是顶层 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 上

4.4 constexpr 和常量表达式

4.4.1 常量表达式

常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的 const 对象也是常量表达式。

一个对象(或表达式)是不是常量表达式由它的数据类型初始值共同决定。例如:

const int maxa = 20;    //是常量表达式
const int mina = maxa + 1;//是常量表达式
int num = 10;          //不是常量表达式
const int result = get_result();//不是常量表达式

对最后一个语句做一下解释:尽管result本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。

4.4.2 constexpr 变量

在一个复杂系统中,几乎不可能分辨出一个初始值到底是不是常量表达式。将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。例子:

constexpr int a = 20;       //20 是常量表达式
constexpr int b = a + 1;  //a + 1 是常量表达式constexpr int c = size();   //只有当 size() 是一个 constexpr 函数时,才是一条正确的声明语句

注:constexpr函数应该足够简单以使得编译时就可以计算其结果,这样就能用constexpr函数去初始化constexpr变量了。

一般来说,如果认定了变量是一个常量表达式,那就把它声明成 constexpr 类型。

4.4.3 字面值类型

常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制,因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值常量”(literal type)。

算术类型、引用和指针都属于字面值类型。自定义类、IO库、string类型则不属于字面值类型,也就不能定义成constexpr

一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。

函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。

4.4.4 指针和 constexpr

constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:

const int *p = nullptr;         //p 是一个指向整型常量的指针
constexpr int *q = nullptr;        //q 是一个指向整数的常量指针

constexpr会把它所定义的对象置为了顶层const

与其他常量指针类似,指针既可以指向常量,也可以指向一个非常量:

constexpr int *np = nullptr;    //np 是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int i = 42;          //i 的类型是整型常量
//注意:i 和 j 必须定义在函数体之外,下面的语句才合法
constexpr const int *p = &i;   //p 是常量指针,指向整型常量 i
constexpr int *p1 = &j;            //p1 是常量指针,指向整数 j

5. 处理类型

5.1 类型别名

类型别名(type alias)是一个名字,它是某种类型的同义词。使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。

两种定义类型别名的方法:

//方法1,使用关键字 typedef
typedef double wages;   //wages 是 double 的同义词
typedef wages base, *p; //base 是 double 的同义词,p 是 double* 的同义词//方法2,使用别名声明,用关键字 using 作为别名声明的开始
using SI = Sales_item; //SI 是 Sales_item 的同义词
//把等号左侧的名字规定成等号右侧类型的别名

如果某个类型别名指代的是复合类型或常量,那么把它用到声明语句里就会产生意想不到的后果。下面用例子说明:

typedef char *pstring;
const pstring cstr = 0;        //cstr 是指向 char 的常量指针
const pstring *ps;          //ps 是一个指针,它的对象是指向 char 的常量指针

两条声明语句的基本数据类型都是const pstringconst是对给定类型的修饰。pstring实际上是指向char的指针,因此,const pstring就是指向char的常量指针,而非指向常量字符的指针。

但如果直接错误地进行下面的理解:

const char *cstr = 0;       //直接进行替换,这是错误的!

替换后的语句的含义和之前的完全不同!替换后表达的含义是cstr是一个指向const char的指针。之所以产生这样的后果,是因为前后两个声明语句的基本数据类型发生了改变:用pstring声明时,基本数据类型是指针;替换后,基本数据类型是const char*是声明符的一部分。

5.2 auto 类型说明符

使用auto类型说明符可以让编译器替我们去分析表达式所属的类型。和原来那些只对应一种特定类型的说明符不同,auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值

使用auto也能在一条语句中声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一样:

auto i = 0, *p = &i;       //正确:i 是整数、p 是整型指针
auto sz = 0, pi = 3.14;       //错误:sz 和 pi 的类型不一致
  • 使用引用作为初始值时,编译器会以引用对象的类型作为auto的类型。

  • auto一般会忽略掉顶层const,同时底层const则会保留下来,比如当初始值是一个指向常量的指针时:

    int i = 0;
    const int a = i, &ra = a;
    auto b = a;        //b 是一个整数(a 的顶层 const 特性被忽略掉了)
    auto c = ra;   //c 是一个整数
    auto d = &i;   //d 是一个整型指针(整数的地址就是指向整数的指针)
    auto e = &ci;  //e 是一个指向整数常量的指针(对常量对象取地址是一种底层 const)
    
  • 如果需要推断出的auto类型是一个顶层const,需要明确指出:

    const int a = 0;
    const auto f = a;  //a 的推演类型是 int,f 是const int
    
  • 还可以将引用的类型设为auto,此时原来的初始化规则仍然适用:

    const int a = 0;
    auto &g = a;       //g 是一个整型常量引用,绑定到 a
    auto &h = 1;       //错误:不能为非常量引用绑定字面值
    const auto &j = 1; //正确:可以为常量引用绑定字面值
    
  • 要在一条语句中定义多个变量,切记,符号&*只从属于某个声明符而非基本数据类型的一部分,因此初始值必须是同一种类型:

    int i = 0;
    const int a = i;
    auto k = a, &l = i;       //k 是整数,l 是整型引用
    auto &m = a, *p = &a; //m 是对整型常量的引用,p 是指向整型常量的指针
    auto &n = i, *p2 = &a;    //错误:i 的类型是 int 而 &a 的类型是 const int
    

5.3 decltype 类型指示符

decltype类型说明符选择并返回操作数的数据类型,编译器并不实际计算表达式的值:

decltype(f()) sum = x;      //sum 的类型就是函数 f 的返回类型

decltype处理顶层const和引用的方式与auto有些许不同。如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):

const int a = 0, &ra = a;
decltype(a) x = 0;     //x 的类型是 const int
decltype(ra) y = x;        //y 的类型是 const int&,y 绑定到变量 x
decltype(ra) z;         //错误:z 是一个引用,必须初始化

需要指出的是,引用从来都作为其所指对象的同义词出现,只有在decltype处是一个例外。

下面给出一个decltype和引用的例子:

//decltype 的结果可以是引用类型
int i = 10, *p = &i, &r = i;
decltype(r) a;      //正确:a 是 int& 型
decltype(r + 0) b; //正确:b 是 int 型//如果表达式是解引用操作,则 decltype 将得到引用类型
decltype(*p) c = i;    //正确:c 是 int& 型,也进行了初始化
decltype(*p) d;     //错误:d 是 int& 型,必须初始化

即表达式的内容是解引用操作时,decltype将得到引用类型

从上面我们可以看出,decltype的结果类型与表达式密切相关,这是decltypeauto的重要区别之一。还有一种情况需要特别注意,对于decltype所用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的是一个不加括号的变量,则得到的结果就是该变量的类型;如果给变量加上了一层或多层括号,编译器就会把它当成一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:

// decltype 的表达式如果是加上了括号的变量,结果将是引用
int i = 1;
decltype((i)) a;    //错误:a 是 int&,必须初始化
decltype(i) b;      //正确:b 是一个未初始化的 int

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

6. 自定义数据类型

从最基本的层面理解,数据结构是把一组相关的数据元素组织起来然后使用它们的策略和方法。

C 里面的结构体在 C++ 中改称为类,是一种只有数据成员没有成员函数的类。不同的是,C++ 新标准规定可以为数据成员提供一个类内初始值。创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将被默认初始化

提供类内初始值时,或者放在花括号里面,或者放在等号右边,但要注意不能用圆括号

可以在函数体内定义类,但是这样的类毕竟受到了一些限制。所以,类一般都不定义在函数体内。当在函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义。而且,如果要在不同文件中使用同一个类,类的定义就必须保持一致。

为了确保各个文件中类的定义一致,类通常被定义在头文件中,而且累所在头文件的名字应与类的名字一样。例如,库类型string在名为string的头文件中定义。

头文件通常包含那些只能被定义一次的实体,如类、constconstexpr变量等。头文件也经常用到其他头文件的功能(所以就会存在重复包含的问题)。

下面给出重复包含头文件的解决方案:

比如我们的Dog.h中要用到string,所以包含了头文件string.h,在my_dog.cpp中我们也要用到string,所以再一次包含了string.h

确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor)。预处理器是在编译之前执行的一段程序,可以部分地改变我们所写的程序。C++ 程序还会用到的一项预处理功能是头文件保护符(header guard),头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef当且仅当变量意定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif指令为止。使用示例:

#ifndef STRING_H
#define STRING_H
#include<string>
#endif

Dog.hmy_dog.cpp中要写#include<string>语句时,都按照上面这样修改,就可以解决重复包含的问题了。

还有一种更为方便的解决重复包含的方案(推荐使用这种),就是在文件最开头加上#pragma once,CSDN 上一篇讲解它的用法的:C/C++ 中#pragma once的使用。


下面给出上一篇文章中的Dog类的完整程序,共有三个源文件:Dog.hDog.cppmy_dog.cpp

//Dog.h
#pragma once
#include<string>
class Dog {public:Dog(int dog_age, std::string dog_breed);//构造函数,创建实例时需要传入参数void roll();//打滚儿int get_age();//输出年龄std::string get_breed();//输出品种
private:int age;//年龄std::string breed;//品种
};
//Dog.cpp
#pragma once
#include"dog.h"
#include<iostream>
#include<string>Dog::Dog(int dog_age, std::string dog_breed){age = dog_age;breed = dog_breed;
}void Dog::roll() {std::cout << "The dog is rolling over!" << std::endl;
}int Dog::get_age() {return age;
}std::string Dog::get_breed() {return breed;
}
#pragma once
#include<iostream>
#include"dog.h"int main() {Dog my_dog(3, "Teddy");std::cout << "My dog is " << my_dog.get_age() << '.' << std::endl;std::cout << "The breed of my dog is " << my_dog.get_breed() << '.' << std::endl;my_dog.roll();return 0;
}

在 VS 2019 中以非调试模式运行项目,运行结果:


【C++】C++ 基础——变量和基本类型的内容到这里就结束了。

下一篇预告:【C++】C++ 基础——字符串、向量和数组

链接指路:
上一篇:【C++】开始
下一篇:【C++】C++ 基础——字符串、向量和数组

【C++】C++基础 —— 变量和基本类型相关推荐

  1. 下标索引必须为正整数类型或逻辑类型_python量化基础 | 变量和简单的数据类型,零基础都可以看懂...

    编辑 | Cowboy 校对 | 李明 来源 | 牛角财经 目的 | python量化基础 | 变量和简单的数据类型,零基础都可以看懂!!! python教程 从入门到高级(免费) 特点:案例基于金融 ...

  2. Js基础——变量类型和计算

    JS基础--变量类型和计算 一.基本数据类型 二.引用类型 三.typeof判断数据类型 四.浅拷贝与深拷贝 4.1 浅拷贝 4.2 深拷贝 五.数据类型转换 5.1== 和 === 一.基本数据类型 ...

  3. python中的变量是动态类型的什么意思_Python零基础入门(一):对Python的简单认识...

    点击蓝字 关注浅韵 一起划水 写在前面 期末考试告一段落之后,这个公众号又活了. 漫长的假期,想玩也是肯定的,但是发现自己想学的东西也很多,想写的东西也很多,所以给自己来了一个假期整活计划. 在这个假 ...

  4. c语言基础-变量类型和定义

    c语言基础-变量类型和定义 一.常量与变量 常量:固定的一个值 例如1 2 3 A B 变量:一个固定的内存地址,其中存储的值可以变化,这个地址的名字在程序体现就是变量 例子: #include &q ...

  5. Python|Git remote|hosts|PyCharm常用快捷键|变量转换|命名|类型|运算符|分支|调整tab|循环|语言基础50课:学习记录(1)-项目简介及变量、条件及循环

    目录 系列目录 均引自原项目地址: Python语言基础50课简介及相关网址 修改 hosts 文件 解决 GitHub 上图片无法显示 视频资源 Python语言基础50课代码等文件资源 表1. P ...

  6. python基础-变量,变量类型,字符串str,元组tuple,列表list,字典dict操作详解(超详细)

    python基础--变量 (文章较长,若需要单独看某一个点,可点击目录直接跳转) 文章目录 python基础--变量 1. 变量 2. 变量类型 2.1数字类型 2.2 字符串 2.3 列表 2.4 ...

  7. iOS编程基础-Swift(三)-变量与简单类型

    Swift入门指南(iOS9 Programming Fundamentals With swift) 第三章 变量与简单类型 深入介绍变量的声明和初始化,介绍所有主要的Swift內建简单类型: 这里 ...

  8. Javascript基础回顾 之(一) 类型

    本来是要继续由浅入深表达式系列最后一篇的,但是最近团队突然就忙起来了,从来没有过的忙!不过喜欢表达式的朋友请放心,已经在写了:) 在工作当中发现大家对Javascript的一些基本原理普遍存在这里或者 ...

  9. Javascript 基础—变量 运算符

    经过找工作笔试的洗礼,感觉自己js语法方面掌握的不是很系统,今天来梳理下--变量以及运算符. 基础篇 和C语言的不同点:是一种弱类型语言,申明变量时不需要指定类型:变量名的命名方法也有不同:简单类型种 ...

最新文章

  1. redissession有容量上限吗_空气炸锅值得买吗?
  2. Django博客系统注册(创建用户模块应用)
  3. go打造以太坊合约测试框架
  4. Windows 8 Hello World
  5. Android Caused by: java.lang.IllegalArgumentException: column '_id' does not exist
  6. Flink 1.11 与 Hive 批流一体数仓实践
  7. 极乐技术周报(第十六期)
  8. 在Vue中使用JSX,很easy的
  9. 设计灵感在哪里?集设网海纳百川,智慧的聚集地
  10. 2021-09-08 集成学 习 思想概述
  11. java 课程设计题目_Java课程设计题目有哪些?Java课程设计题目汇总
  12. 长安大学微型计算机原理与接口技术答案,长安大学微机原理与接口技术AB卷试题与答案.doc...
  13. 计算机蓝屏代码0x0000007b,win7开机蓝屏提示STOP:0X0000007B的解决方法
  14. 解决苹果手机双击页面放大的问题
  15. Ubuntu终端快捷复制粘贴
  16. 基于ThingsBoard二次开发的物联网平台ThingsKit-Link白皮书
  17. 网吧免费上网的7种武器
  18. Windows命令行学习笔记
  19. 在Xml中加注释的方法
  20. MATLAB模型预测控制(MPC)示例

热门文章

  1. 弄懂ppi、dpi、pt、px、dp、dip、sp之间的关系
  2. 操作系统 — 生产者消费者模型
  3. 桥梁通服务器没有响应,[桥梁通问答.doc
  4. CSS3实例——表格
  5. 计算机网络class4(时延、时延带宽积、RTT和利用率)
  6. ❤ javaJDK isn‘t specified for module问题解决
  7. 求最大公约数和最小公倍数(更相减损法/辗转相除法)
  8. Compose自定义实战
  9. sipp模拟freepbx分机测试(SIP协议调试)
  10. thinkPHP+vue医院核酸检测预约挂号系统 nodejs微信小程序