2.变量和基本类型
2.1 变量
2.1.1 初始值
初始化不是赋值,初始化的含义是创建变量时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新值来替代
2.1.2 列表初始化
intunits_sold=0;
intunits_sold= {0};
intunits_sold{0};
2.1.3 默认初始化
定义于任何函数体之外的变量被初始化为0
定义在函数体内部的内置类型将不被初始化
一个未被初始化的内置类型变量的值是未定义的
一些类要求每个对象都显式初始化
此时如果创建了一个该类的对象而未对其做明确的初始化操作,将引发错误
定义于函数体内的内置类型的对象如果没有初始化,则其值未定义。类的对象如果没有显式地初始化,则其值由类确定
2.1.4 变量声明和定义的关系
声明
声明使得名字为程序所知
一个文件若想使用别处定义的名字则必须包含对那个名字的声明
规定变量的类型和名字
定义
负责创建与名字关联的实体
规定变量的类型,名字和申请存储空间
1. 如果想声明一个变量而非定义它,就在变量名前加关键字extern
2. 任何包含了显式初始化的声明即为定义
externinti; //声明i而非定义i
inti; //定义i而非声明i
enterninti=3.14; //定义i
3. 变量只能被定义一次,但可以多次声明
如果在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现且只能出现在一个文件中,
而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义
2.1.5 标识符
C++的标识符由字母,数字和下划线组成,其中必须以字母或下划线开头。标识符没有长度限制,但对大小写敏感
标识符命名规范:
标识符要体现具体含义
变量名一般用小写字母,如index,不要使用Index或INDEX
用户自定义的类名一般以大写字母开头,如Sales_item
如果标识符由多个单词组成,则单词间应有明显区分,如student_loan或studentLoan,不要使用studentloan
2.2 复合类型
复合类型是基于其他类型定义的类型。本章介绍引用和指针
2.3.1 引用
为对象起了另外一个名字。
引用不是对象
引用与它的初始值对象一直绑定在一起,无法重新绑定到另一个对象
必须初始化
为引用赋值,实际上是赋值给了绑定对象
允许一条语句中用多个引用
不能与某个字面值或某个计算结果绑定在一起
必须同类型
intival=1024;
int&refval=ival; // refval指向ival(是ival的另一个名字)
int&refval2; // 报错,必须初始化
intrefval=2; // 把值赋给了ival
intii=refval; // 与ii = ival执行结果一样
int&refval3=refval; // 就是与ival绑定在一起
inti=refval; // i被初始化为ival的值
int&refval=10; // 报错:引用类型的初始值必须是一个对象
doubledval=3.14;
int&refval5=dval; // 报错:此处引用类型的初始值必须是int型对象
2.3.2 指针
C++程序最好用nullptr初始化空指针,同时尽量避免使用NULL
建议初始化所有指针
void* 是一种特殊的指针类型,可用于存放任意对象的地址
2.3.3 理解复合类型的声明
2.3.3.1 定义多个变量
一条定义语句中,声明符的形式可以不同
inti=1024,*p=&i, &r=i;
int*放在一起仅仅起到修饰后面一个变量名的作用,而不是全部
int*p1,p2; //p1为指针,p2为整型
2.3.3.2 指向指针的指针
2.3.3.3指向指针的引用
面对一条比较复杂的指针或引用的声明语句时,从右向左阅读有助于弄清楚它的真实含义
inti=42;
int*p;
int*&r=p; // r是一个对指针的引用
r=&i; //r引用了一个指针,因此给r赋值&i就是令p指向i
*r=o; //解引用r得到i,也就是p指向的对象,将i的值改为0
2.3 const限定符
2.3.1 const介绍
const限定的对象,其值不能在改变
任何试图为const限定的对象赋值的行为都会报错
const限定的对象必须初始化
如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要,新对象与原来的对象没什么关系了
inti=42;
constintci=i; // 正确:i的值被拷贝给了ci
intj=ci; // 正确:ci的值被拷贝给了j,j与ci无关,即不被const限定
const对象仅在文件内有效
当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量
某些时候我们不希望编译器为每个文件分别生成独立的变量。解决办法是,对于const变量不管是声明还是定义都添加extern关键字
2.3.2 const的引用(简称为常量引用)
与普通引用不同,对常量的引用不能用作修改它所绑定的对象
不能通过引用去改变常量的值
constintci=42;
constint&r1=ci; // 正确
r1=1024; // 错误:r1是对常量的引用
int&r2=ci; // 错误:试图让一个非常量引用指向一个常量对象
允许为一个常量引用绑定非常量的对象,字面值
inti=42;
constint&r1=i; // 正确:允许将一个const int绑定到interesting上
constint&r2=42; // 正确
constint&r3=r1*2; // 正确
int&r4=r1*2; // 错误
临时量:当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象
doubledval=3.14;
(constinttemp=dval;)
(constint&ri=temp;) // temp为临时量,ri相当于绑定了一个临时量
constint&ri=dval; // 正确
必须为常量
doubledval=3.14;
int&r1=dval; // 报错
constint&r2=dval; // 正确
对const的引用可以引用一个并非const的对象
inti=42;
int&r1=i;
const&r2=r1; // 正确
2.3.3 指针和const
2.3.3.1 指针和const
指向常量的指针不能用于改变其所指的值
存放常量对象的地址,只能使用指向常量的指针
允许令一个指向常量的指针指向一个非常量对象
2.3.3.2 const指针
常量指针必须初始化,一旦初始化完成,则它的值就不能在改变了
把*放在const之前用以说明指针是一个常量
interrNumb=0;
int*constcurErr=&errNumb; // curErr将一直指向errNumb
constdoublepi=3.14;
constdouble*constpip=π // pip是一个指向常量对象的常量指针
指针本身是常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。
(怎么通过指针修改其所指对象的值)
2.3.4 顶层const和底层const
顶层const:指针本身是一个常量。更一般的,顶层const可以表示任意的对象是常量。
底层const:指针所指的对象是一个常量。更一般的,与指针和引用等复合类型的基本类型部分有关
用于声明引用的const都是底层const
顶层const拷贝是不受影响
底层const拷贝
执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格
两个对象的数据类型必须能够转换
非常量可以转换为常量,反之不行
inti=0;
int*constp1=&i; // 顶层const
constintci=42; // 顶层const
constint*p2=&ci; // 底层const,可以改变p2的值
constint*constp3=p2; // 右边const为底层const,左边为顶层const
constint&r=ci; // 底层const
//顶层const拷贝
i=ci; // 正确
p2=p3; // 正确
//底层const拷贝
int*p=p3; // 错误:p3包含底层const的定义,而p没有
p2=p3; // 正确
p2=&i; // 正确:int*能转换为const int*
int&r=ci; // 错误:普通的int&不能绑定到int常量上
constint&r2=i; // 正确:const int&可以绑定到一个普通int上
2.3.5 constexpr 和常量表达式
2.3.5.1 常量表达式
指值不会改变并且在编译过程就能得到计算结果的表达式
constintmax_files=20; // max_files是常量表达式
constintlimit=max_files; // limit是常量表达式
intstaff_size=27; // 不是:只是普通的int,没有const
constintsz=get_size(); // 不是
2.3.5.2 constexpr变量
允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式
一般来说,如果你认定变量是一个常量表达式,那就把它声明为consrexpr类型
constexprintmf=20; // 是
constexprintlimit=mf+1; // 是
constexprintsz=size(); // 只有当size是一个constexpr函数时,才是一个正确的声明语句
2.3.5.3 字面值类型
2.3.5.4 指针和constexpr
限定符constexpr仅仅对指针有效,与指针所指的对象无关
constexprint*q=nullptr; // q是一个指向整数的常量指针
constint*p=nullptr; // p是一个指向整型常量的指针
constexpr指针也可以指向一个非常量
2.4 处理类型
2.4.1 类型别名
是一个名字,他是某种类型的同义词。只要是类型的名字能出现的地方,就能使用类型别名
typedef关键字
typedefdoublewages;
typedefwagesbase,*p; // p是double*的类型别名
using关键字(别名声明)
用关键字using作为别名的开始
其后紧跟别名和等号
把等号左侧的名字规定成等号右边类型的别名
usingSI=Sales_item;
指针,常量和类型别名
typedefchar*pstring;
constpstringcstr=0; // cstr 是指向char的常量指针
constchar*=0; // 是对const pstring cstr的错误理解,指向const char的指针
2.4.2 auto类型说明符
2.4.2.1 auto
让编译器代替我们去分析表达式的类型
auto定义的变量必须有初始值
可以在一条语句中声明多个变量
语句中所有变量类型必须一致
autoitem=val1+val2; // item类型与val1和val2一致
autoi=0,*p=&i; // 正确
autoi=0,j=3.14; // 错误,类型不一致
2.4.2.2 复合类型,常量和auto
编译器以引用对象的类型作为auto类型
auto一般会忽略掉顶层const,同时底层const则会保留下来
constintci=i,&cr=ci;
autob=ci; // 整数
autoc=cr; // 整数
autod=&i; // 整型指针
autoe=&ci; // 指向整型常量的指针
如果希望推断出的auto类型是一个顶层const,需要明确指出:
constautof=ci;
可以将引用的类型设为auto,此时原来的初始化规则仍然适用
auto&g=ci; //
auto&h=42; // 错误:不能为非常量引用绑定字面值
constauto&j=42; // 正确
符号&和*只属于某个声明符,而非基本类型的一部分
2.4.3 decltype类型指示符
2.4.3.1 decltype
选择并返回操作数的数据类型
不实际计算表达式的值
decltype(f()) sum = x; // sum的类型就是函数f()的返回类型
编译器并不实际调用函数f
如果decltype使用的表达式是一个变量,则decltype返回该类型的变量(包括顶层const和引用在内)
constintci=0,&cj=ci;
decltype(ci) x=0; // x的类型是const int
decltype(ci) y=x; // y的类型是const int&,y绑定到变量x
decltype(cj) z; // 错误,引用要初始化
2.4.3.2 decltype和引用
decltype返回表达式结果对应的类型
加括号与不加括号会产生不同的结果
加括号:编译器会把它当作表达式。变量是一种可以作为赋值语句左值的特殊表达式,所以这样的decltype就会的到引用类型
不加括号:得到的结果就是该变量的类型
decltype((i)) d; // 错误:为引用,必须初始化
decltype(i) e; // 正确
2.5 自定义数据结构
2.5.1 定义Sales_data类型
如何定义
以关键字struct开始
紧跟着类名和类体
类体由花括号包围形成了一个新的作用域
类的内部定义的名字必须唯一,但是可以与类外部定义的名字重复
类体表示结束的花括号后面必须跟一个分号
这是因为类体后面可以紧跟变量名以示该类型对象的定义
structSales_data{
std::stringbookNo;
ubsignedunits_sold=0;
doublerevenue=0.0;
};
类初始值:初始化数据成员,没有初始化的成员将被默认初始化
2.5.2 使用Sales_data类
2.5.2.1 添加两个Sales_data类
头文件
2.5.2.2 Sales_data对象输入数据
string类型其实就是字符的序列
>> : 读入字符串
<<:写入字符串
==:比较字符串
doubleprice=0;
std::cin>>data1.bookNo>>data1.units_sold>>price;
data1.revence=data1.units_sold*price;
2.5.2.3 输出两个Sales_data对象的和
2.5.3 编写自己的头文件
头文件一般包含只能被定义一次的实体
2.5.3.1 预处理器概述
确保头文件多次包含仍能安全工作的常用技术
头文件保护符(可有效防止重复包含发生)
#define 指令把一个名字设定为预处理器变量
#ifdef:当且仅当变量已经定义为真
#ifndef:当且仅当变量为定义为真
一旦检验为真,则执行操作直到遇到#endif指令为止
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<string>
structSales_data{
std::stringbookNo;
unsignedunits_sold=0;
doublerevenue=0.0;
};
#endif
预处理器变量无视C++语言中关于作用域的规则
整个程序中的预处理器变量包括头文件保护必须唯一