C++Primer学习笔记_第二章 变量和基本类型

第二章 变量和基本类型

————————————————————————————————————————————————————————————

2.1 基本内置类型

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

2.1.1 算术类型

算术类型分为两种:整型integral,包括字符和布尔值在内)和浮点型
算术类型
基本字符类型是char,其他字符类型用于拓展字符集。

除字符和布尔类型之外,其他整型用来表示(可能)不同尺寸的整数。

浮点型可用来表示单精度、双精度和拓展精度值。
注:执行浮点数计算最好选用double。在算术表达式中不要选用字符型或布尔型的值。

带符号类型和无符号类型

除去布尔型和拓展的字符型之外,其他整型可以划分为带符号的(signed)无符号的(unsigned) 两种,带符号的能表示正数、负数和0,无符号的只能表示大于0的数。

与其他整型不同,字符型被分为了三种:char、signed char和unsigned char。signed char和char并不完全一样,字符型有三种,字符的表现只有两种:有符号的和无符号的。类型char的实际表现由编译器来决定。

无符号类型中所有比特都用来存储值。例如,unsigned char可用来表示0-255之间的任一值。
注:当明确知道数值不可能为负时,选用无符号类型

2.1.2 类型转换

类型转换就是将对象从一种给定的类型**转换(convert)**为另一种相关类型。

当在程序的某处我们使用了一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。

bool b=42;//b为真
int i=b;//i的值为1
i=3.14;//i的值为3
double PI=i;//PI的值为3.00
unsigned char c=-1;//假设char占8比特,则c的值为255
signed char c2=256;//假设char占8比特,则c2的值未定义

当我们赋给带符号类型一个超过它表示范围的数时,结果是未定义的(undefined),此时,程序可能继续工作,可能崩溃,可能产生垃圾数据。

含有无符号类型的表达式

当一个算术表达式中既有无符号数又有int类型的数,那么这个int类型的数会被转换成无符号数

unsined u=10;
int i=-42;
std::cout<<i+i<<std::endl;//输出-84
std::cout<<i+u<<std::endl;//如果int占32位,则输出4294967264

当从无符号数中减去一个值时,无论这个值是不是无符号数,都要确保最后的结构不是一个负数。

unsigned u1=42,u2=10;
std::cout<<u1-u2<<std::endl;//正确,输出32
std::cout<<u2-u1<<std::endl;//正确,不过输出的值是取模的结果

注:切勿混用带符号类型和无符号类型

2.1.3 字面值常量

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

整型和浮点型字面值

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

20/*十进制数*/	020/*八进制数*/	0x20/*十六进制数*/

整型字面值具体的数据类型由它的值和符号决定。默认情况下,十进制数是带符号的,而八进制数和十六进制数可能是无符号的,也可能是带符号的。

整型字面值可以被存储在带符号数据类型中。严格来说,整型字面值不可能是负数,如-42中的负号只是对字面值取负值。

浮点型字面值表现为一个小数或以科学计数法表示的指数,其中指数部分用E或e来表示。

3.14159		3.14159E0		0.		0e0		.001

默认的,浮点型字面值是一个double。

字符和字符串字面值

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

'a'				//字符型字面值
"Hello World"	//字符串型字面值

字符串字面值的类型实际上是常量字符构成的数组(array)。编译器在每个字符串的结尾加上一个’\0’,因此每个字符串的长度实际上比它的内容多1。

转义序列

有两类字符程序员不可以直接使用,一种是 不可打印(nonprintable) 的字符,因为它们没有可视的图符,另外一种是在C++中含有特殊含义的字符(单引号、双引号、问好、反斜线)。在这些情况下需要用到转义序列(escape sequence)
转义序列
在程序中,转义序列被当作一个字符。

我们也可使用泛化的转义字符,其形式是\x后跟一个或多个十六进制的数字,或者\后面紧跟1个、2个或3个的八进制数字,其中数字部分表示的是字符对应的数值。
泛化的转义序列
我们也可像只用普通字符一样使用C++定义的转义序列

std::cout<<"Hi \x4dO\115!\n";//输出Hi MOM!
std::cout<<'\115';//输出M

如果\后面的八进制数超过3个,则只有\和前3个数字构成转义序列,如/1234为转义序列\123对应的字符和字符4。\x则要用到跟在它后面所有的数字(可能会报错)。

指定字面值的类型

通过添加如下的前缀或后缀,可以改变整型、浮点型和字符型字面值的默认类型。
前缀后缀

布尔字面值和指针字面值

true和false是布尔类型的字面值:

bool test=false;

nullptr是指针字面值。
————————————————————————————————————————————————————————————

2.2 变量

变量提供一个具名的,可供程序操作的存储空间。变量(variable)对象object,指一块能存储数据并拥有某种类型的内存空间)一般可以互换使用。

2.2.1 变量定义

变量定义的基本形式是:首先是类型说明符(type specifier),随后紧跟由一个或多个变量名组成的列表。

int sum=0,value,
	units_sold=0;
Sales_item item;
//string是一种库类型,表示一个可变长的字符序列
std::string book("0-201-78345-X");
初始值

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

列表初始化
int units_sold=0;
int units_sold={0};
int units_sold{0};
int units_sold(0);

这种用花括号来初始化变量的形式都叫做列表初始化(list initialization)

当用于内置类型的变量,列表初始化有一种特点:如果列表初始化且初始值存在丢失信息的风险,则编译器会报错。

默认初始化

如果定义变量时没有指定初值,那么变量会被默认初始化(default initialization)

默认值由变量类型来决定,定义变量的位置也会对此由影响。

如果是内置类型的变量未被显式初始化,它的值由定义的位置决定。定义于任何函数体之外的变量将被初始化为0。一种例外情况是,定义在函数体内部的内置类型变量不被初始化(uninitialized)。一个不被初始化的内置类型变量的值是未定义的,试图拷贝或以其他方式访问这个值将引起错误。

每个类各自决定其初始化对象的方式。而且,是否允许不经初始化就定义对象也由类自己决定。如果类运行这种行为,它将决定对象的初始值到底是什么。
注:定义于函数体内的内置类型的对象如果没有初始化,其值是未定义的。类的对象如果没有显式地初始化,则其值由类决定

2.2.2 变量声明和定义的关系

C++语言支持分离式编译(separate complication) 机制 ,该机制允许将程序分割成若干个文件,每个文件可被独立编译。

为了支持分离式编译,C++语言将声明与定义区分开来。声明(declaration) 使得名字被程序所知,一个文件如果想用别处所定义的名字则必须包含对那个名字的声明。而定义(definition) 负责创建与名字关联的实体。

变量和定义都声明规定了变量的类型和名字,而定义还将为变量申请存储空间,也可能会为变量赋一个初始值。

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量。

extern语句如果包含初始值就不再是声明,而变成定义了。

变量只能被声明一次,但可以被多次定义。

如果要在多个文件中使用同一个变量,就必须将声明与定义分离。此时,变量的定义必须出现在且只能出现在同一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

关键概念:静态类型
C++是一种静态类型(statically typed) 语言,其含义是在编译阶段检查类型。其中,检查类型的过程被称为类型检查(type checking)
在C++语言中,编译器负责检查数据类型是否支持要执行的运算。如果试图执行类型不支持的运算,编译器将报错并且不会生成可执行文件。

2.2.3 标识符

C++的标识符(identifier) 由字母、数字、下划线组成,其中必须以字母或下划线开头,长度没有限制但对大小字母有区分。

用户自定义的标识符不能连续出现两个下划线,也不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头。

变量命名规范

· 标识符要能体现实际含义。
· 变量名一般使用小写字母,如index,不要写成Index或INDEX。
· 用户自定义的类名一般以大写字母开头,如Sales_item。
· 如果标识符由多个单词组成,则单词间应有明显区分,如student_loan或studentLoan,不要使用studentloan。
C++关键字和操作符替代名

2.2.4 名字的作用域

作用域(scope) 是程序的一部分,C++语言中大多数作用域都以花括号分隔。

同一个名字在不同的作用域可能指向不同的实体。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端作为结束。

主函数main定义在所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有全局作用域(global scope)。函数体内的变量具有块作用域(block scope)

嵌套的作用域

作用域能彼此包含,被包含(或被嵌套)的作用域称为内层作用域(inner scope),包含着别的作用域的作用域被称为外层作用域(outer scope)

#include<iostream>
//该程序仅用于说明:函数内部不宜定义与全局变量同名的新变量
int reused = 42;//reused拥有全局作用域
int main()
{
	int unique = 0;//unique拥有块作用域
	//输出#1:使用全局变量reused;输出 42 0
	std::cout<<reused<<" "<<unique<<std::endl;
	int reused = 0;//新建局部变量reused,覆盖了全局变量reused
	//输出#2:使用局部变量reused;输出 0 0
	std::cout<<reused<<" "<<unique<<std::endl;
	//输出#3:显式地访问全局变量reused;输出 42 0
	std::cout<<::reused<<" "<<unique<<std::endl;
	return 0;
}

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

2.3 复合类型

复合类型(compound type) 是指基于其他类型定义地类型。C++语言由几种复合类型,如引用和指针。

一条声明语句由一个基本数据类型(base type) 和紧随其后的一个声明符(declarator) 列表组成。每个声明符命名了一个变量并指定该变量为与基本数据类型有关的某种类型。

2.3.1 引用

引用(reference,通常指的是左值引用) 为对象起了另一个名字,引用类型引用另一种类型,通过将声明符写成&d的形式来引用另一种类型,其中d是声明的变量名。

int ival=1024;
int &refVal=ival;//refVal指向ival(refVal是ival的别名)
int refVal2;//报错,引用必须初始化

程序把引用和它的初始值绑定(bind) 在一起,而不是将初始值拷贝给引用,无法令引用绑定到另外一个对象。

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

引用的定义

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

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

2.3.2 指针

指针(pointer) 是指向另一种类型的复合类型。

指针本身就是一个对象,允许对指针进行赋值或者拷贝。在指针的生命周期内它可以指向多个不同的对象。指针可以不在定义时赋初值。

定义指针的方法是将声明符写成*d的形式,其中d是变量名。允许在一个语句中定义多个指针,每个指针变量名前必须以 * 符号开头。

获取对象的指针

指针存放某个对象的地址,要想获取这个地址,必须使用取地址符(&)

引用不是对象,所以不能定义指向引用的指针。

一般情况下,指针的类型要与它指向对象的类型一致。

指针值

指针的值(即地址)应属下列4种状态之一:
1.指向一个对象;
2.指向紧邻对象所占的下一个位置;
3.空指针,意味着指针没有指向任何位置;
4.无效指针,也就是上述情况之外的其他值。

试图访问或拷贝无效指针的值都将引发错误,访问无效指针的后果无法预计。

访问第二种和第三种指针的对象行为不被允许,后果也无法预计。

利用指针访问对象

如果指针指向了一个对象,则允许用解引用符(操作符 * ) 来访问该对象。

对指针解引用会得到指针所指向的对象,因此给解引用的结果赋值,实际上就是给指针所指向的对象赋值。

关键概念:某些符号有多重含义:
像&和这样的符号,既能用作表达式的运算符,也能作为声明的一部分出现,符号的上下文决定了符号的意义。
在声明语句种,&和
用于构造复合类型,在表达式中,它们又转变为运算符。

空指针

空指针(null pointer) 不指向任何对象。几个生成空指针的方法:

int *p1=nullptr;	//等价于int *p1=0
int *p2=0;			//直接将p2初始化为字面值常量0
int *p3=NULL;		//等价于int *p3=0;

得到空指针最直接的办法是用字面值nullptr来初始化指针。nullptr是一种特殊类型的字面值,它可被转换成任意其他的指针类型。

过去的程序还会用到一个名叫NULL的预处理变量(preprocessor variable) 来给指针赋值,这个变量在头文件cstdlib种定义,它的值是0。预处理变量不属于命名空间std,它由预处理器负责管理。

注:现在的C++程序最好使用nullptr,同时尽量避免使用NULL

把int变量直接赋给指针是错误的操作,即使int变量的值恰好等于0也不行。

建议:初始化所有指针
使用未经初始化的指针是引发运行时错误的一大原因,访问未经初始化指针所引发的后果也是无法预计。
如果使用未经初始化的指针,相当于访问一个本不存在的位置上的本不存在的对象,若指针所占内存空间中恰好由内容,而这些内容又被当作了某个地址,我们很难分清它到底是合法的还是非法的。
如果实在不清楚指针应该指向哪里,就把它初始化为nullptr或0。

赋值和指针

给指针赋值就是令它存放一个新的地址,从而指向一个新的对象。

其他指针操作

只要指针拥有一个合法值,就能将它用在条件表达式中。任何非0指针对应的条件值都是true。

对于两个类型相同的合法指针,可以用相等操作符(==)或不相等操作符(!=)来比较它们,比较的结果是布尔类型。

void* 指针

void*是一种特殊的指针类型,可用于存放不论类型的任意对象的地址。

利用void指针能做的事比较有限:拿它和别的指针比较、作为函数的输入输出,或者赋给另外一个void指针。不能直接操作void*指针所指的对象。

2.3.3 理解复合类型的声明

在同一条定义语句中,虽然基本数据类型只有一个,但是声明符的形式却可以不同。这意味着同一条定义语句可能定义出不同类型的变量:

//i是一个int型的数,p是一个int型指针,r是一个int型引用
int i=1024,*p=&i,&r=i;
定义多个变量

涉及指针或引用的声明一般有两种写法,如:

//第一种,声明符跟着基本数据类型
int* p1,p2;
//第二种,声明符跟着变量名
int *p1,p2;

第一种写法容易让人误会,认为p1,p2都是int型的指针,但实际上只有p1是int型指针,而p2只是int型的值。

指向指针的指针

指针是内存中的对象,允许把指针的地址再放到另一个指针当中。

通过*的个数可以区分指针的级别。

解引用指向指针的指针会得到一个指针,这意味着要访问最原始的那个对象,需要对指针的指针做两次解引用。

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

2.4 const限定符

当我们希望定义一种值不能改变的变量时,可以用关键字const对变量的类型将以限定:

const int bufSize=512;	//正确
bufSize =1024;			//错误:试图向const对象写值
const int k;			//错误:const对象必须初始化
初始化和const

const类型最主要的限制是只能在其类型的对象上执行不改变其内容的操作。

在不改变const对象的操作中有一种是初始化,如果利用一个对象去初始化另外一个对象,则是不是const都无关紧要。

int i=42;
const int ci=i;		//正确
int j=ci;			//正确
默认状态下,const对象仅在文件内有效

有些时候我们想只在一个文件中定义const,而在其他多个文件中声明并使用它。解决方法是对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:

extern const int bufSize;

extern的作用是指明这个变量并非本文件定义使其被其他文件使用。

2.4.1 const的引用

可以把引用绑定在const对象上,我们称之为对常量的引用(reference to const),对常量的引用不能被用作修改它所绑定的对象。

术语:常量引用是对const的引用
C++程序员经常把“对const的引用”简称为“常量引用”(实际上并不存在常量引用)。

初始化和对const的引用

通常引用的类型必须与其所引用对象的类型一致,两种情况例外。

第一种例外情况是在初始化常量引用时允许用任意表达式作为初始值。

int i=42;
const int &r1=i;		//允许将一个const int&绑定到一个普通int对象上
const int &r2=42;		//正确,r2是一个常量引用
const int &r3=r1*2;		//正确,r3是一个常量引用
int &r4=r1*2;			//错误,r4是一个普通的非常量引用

当一个常量引用被绑定到另外一种类型上时,编译器会创建一个临时量(temporary) 对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。

double dval=3.14;
const int &ri=dval;
/*
编译器会优化成:
double dval=3.14;
const int temp=dval;
const int &ri=temp;
*/
对const的引用可能引用一个并非const的对象

常量引用仅对引用可参与的操作进行了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值。

2.4.2 指针和const

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

和常量引用相似,通常情况指针的类型必须与其所指对象的类型一样,但是有两个例外。

第一种例外时允许令一个指向常量的指针指向一个非常量对象。

const指针

常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(即存放在指针中的那个地址)就不能再改变了。

把*放在const关键字之前用以说明指针是一个常量,注意不变的时指针本身的值而不是指向的那个值。

int errNumb=0;
int *const curErr=&errNumb		//curErr将一直指向errNumb;
const double pi=3.14159;
const double *const pip=&pi;	//pip时一个指向常量对象的常量指针

指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。

2.4.3 顶层const

指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层const(top-level const) 表示指针本身是个常量,而用名词底层const(low-level const) 表示指针所指的对象是一个常量。

指针类型既可以是顶层const也可以是底层const。

底层const的限制不能忽视,当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须能够转换。

2.4.4 constexpr和常量表达式

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

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

const int max_files=20;			//max_files是常量表达式
const int limit=max_files+1;	//limit是常量表达式
int staff_size=27;				//staff_size不是常量表达式
const int sz=get_size();		//sz不是常量表达式

尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。

constexpr变量

C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。

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

新标准允许定义一种特殊的constexpr函数。

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

字面值类型

声明constexpr时用到的类型被称为 “字面值类型”(literal type)

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

指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址的对象。

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

允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。

指针和constexpr

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

constexpr指针既可以指向常量也可以指向一个非常量。

2.5 处理类型

2.5.1 类型别名

类型别名(type alias) 是一个名字,它是某种类型的同义词。

有两种方法可用于定义类型别名。传统的方法是使用关键字typedef

typedef double wages;	//wage是double的同义词
typedef wages base,*p;	//base是double的同义词,p是double*的同义词

关键字typedef作为声明语句中的i基本数据类型的一部分出现。

新标准规定了一种新的方法,使用别名声明(alias declaration)来定义类型的别名:

using SI=Sales_item;	//SI是Sales_item的同义词

这种方法用关键字using作为别名声明的开始,其后紧跟别名和等号,其作用是把等号左侧的名字规定成等号右侧类型的别名。

指针、常量和类型别名
typedef char *pstring;
const pstring cstr=0;	//cstr是指向char的常量指针
const pstring *ps;	//ps是一个指针,它的对象是指向char的常量指针
//下面是错误理解
const char *cstr=0;//对const pstring cstr的错误理解

声明语句中用到pstring时,其基本数据类型是指针。可是错误理解里基本数据类型是const char。前者声明了一个指向char的常量指针,改写后的形式则声明了一个指向const char的指针。

2.5.3 auto类型说明符

C++11新标准引入了auto类型说明符,用它就能让编译器替我们去分析表达式所属的类型。auto让编译器通过初始值来推算变量的类型。显然,auto定义的变量必须有初始值。

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

auto i=0,*p=&i;			//正确:i是整数,p是整数指针
auto size=0,pi=3.14;	//错误:sz和pi的类型不一致
复合类型、常量和auto

编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当地改变结果类型使其更符合初始化规则。

首先,当引用被用作初始值时,编译器以引用对象的类型作为auto的类型。

其次,auto一般会忽略掉顶层const,同时底层const则会保留下来。

如果希望推断出的auto类型是一个顶层const,需要明确指出:

const auto f=ci;	//ci的推演类型是int,f是const int

还可以将引用的类型设为auto,此时原来的初始化规则则仍然适用。

要在一条语句中定义多个变量,切记,符号&和*只从属于某个声明符,而非基本数据类型的一部分,因此初始值必须是同一种类型。

2.5.3 decltype类型指示符

C++11新标准引入了第二种类型说明符decltype,它的作用是选择并返回操作数的数据类型。

在此过程中,编译器分析表达式并得到它的类型,却不实际计算表达式的值,编译器也并不实际调用函数,而是得到当调用发生时函数的返回值类型。

如果decltype使用的表达式时一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)。

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

decltype和引用

如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的类型。有些表达式将向decytype返回一个引用类型。

//decltype的结果可以是引用类型
int i=42,*p=&i,&r=i;
decltype(r+0) b;	//正确:加法的结果是int,因此b是一个未初始化的int
decltype(*p) c;		//错误:c是int&,必须初始化

如果表达式的内容是解引用操作,则decltype将得到引用类型。

对于decltype使用的表达式来说,如果变量名加上了一对括号,则得到的类型与不加括号时会有不同。如果decltype使用的时一个不加括号的变量,则得到的结果就是该变量的类型。如果给变量加上了一层或多层括号,编译器就会把它当成是一个表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会得到引用类型:

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

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

2.6 自定义数据结构

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

C++语言允许用户以类的形式自定义数据类型,而库类型string、istream、ostream等也都是以类的形式定义的。

2.6.1 定义Sales_data类型

struct Sales_data{
	std::string bookNo;
	unsigned units_sold=0;
	double revenue=0.0;
};

我们的类以关键字struct开始,紧跟着类名和类体(其中类体部分可以为空)。

类体由花括号包围形成了一个新的作用域。类内部定义的名字必须唯一,但是可以与类外部定义的名字重复。

类体右侧的表示结束的花括号必须写一个分号,这是因为类体后面可以紧跟变量名以示对该类型对象的定义,所以分号必不可少。

一般来说,最好不要把对象的定义和类的定义放在一起。

类数据成员

类体定义类的成员,我们的类只有数据成员(data member)。类的数据成员定义了类的对象的具体内容。

定义数据成员的方法和定义普通变量一样。

C++新标准规定,可以为数据成员提供一个类内初始值(in-class initialize)。创建对象时,类内初始值将用于初始化数据成员。没有初始值的成员将默认初始化。

用户可以使用C++语言提供的另外一个关键字class来定义自己的数据结构。

2.6.2 使用Sales_data类

添加两个Sales_data对象
Sales_data对象读入数据
输出两个Sale_data对象的和

2.6.3 编写自己的头文件

类一般都不定义在函数体内。当在函数体外部定义类时,在各个指定的源文件中可能只有一处该类的定义。而且,如果要在不同文件中使用同一个类,类的定义就必须保持一致。

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

头文件通常包含那些只能被定义一次的实体,如类、cosnt和constexpr对象。

预处理器概述

确保头文件多次包含仍能安全工作的常用技术是预处理器(preprocessor)。预处理器是在编译之前执行的一段代码,可以部分地改变我们所写地程序,如预处理功能 #include

C++程序还会用到地一项预处理功能是头文件保护符(header guard),头文件保护符依赖于预处理变量。预处理变量有两种状态:已定义和未定义。#define指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定地预处理变量是否已经定义:#ifdef当且仅当变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到 #endif指令为止。

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

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
struct Sales_data{
	std::string bookNo;
	unsigned unit_sold=0;
	double revenue=0.0;
};
#endif

注:预处理变量无视C++语言中关于作用域地规则。
整个程序中的预处理变量包括头文件保护符必须唯一,通常的做法是基于头文件中类的名字来构建保护符的名字,以确保唯一性。为了避免与程序中的其他实体发生名字冲突,一般把预处理变量的名字全部大写。
注:头文件即使(目前还)没有被包含在任何其他头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以了,没必须太在乎你的程序到底需不需要。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值