第二章 变量和基本类型

2.1 基本内置类型

基本类型包括算术类型空类型(一般用作函数返回类型)。

2.1.1 算术类型

  1. 算术类型分为两类:整型(包括字符和布尔类型)和浮点型
    C++算术类型
  2. 注意:与其他整型不同,字符型分为三种:char、signed char和unsigned char。注意char和signed char并不一样(取决于编译器)。尽管字符型有三种,但是字符的表现形式却只有两种:带符号的和无符号的。
  3. 如何选择类型:
    当明确知晓数值不可能为负时,选用无符号类型。
    使用int执行整数运算。如果数值超过了int的表示范围,选用long long。
    在算术表达式中不要使用char或bool,只有在存放字符或布尔值时才使用它们。如果需要使用一个不大的整数,那么明确指定它的类型是signed char或unsigned char。
    执行浮点数运算选用double,因为float通常精度不够,而且双精度浮点数和单精度浮点数的计算代价相差无几。long double提供的精度在一般情况下是没有必要的,况且它带来的运行时消耗也不容忽视。

2.1.2 类型转换

  1. 类型所能表示的值的范围决定了转换的过程:
    当赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。
    当赋给带符号类型一个超出它表示范围的值时,结果是未定义的。
  2. 建议:避免无法预知和依赖于实现环境的行为。
  3. 含有无符号类型的表达式:
    把负数转换成无符号数类似于直接给无符号数赋一个负值,结果等于这个负数加上无符号数的模。
    当从无符号数中减去一个值时,不管这个值是不是无符号数,都必须确保结果不能是一个负值。
  4. 提示:切勿混用带符号类型和无符号类型。
unsigned u=10;
int i=-42;
std::cout << i+i << std::endl;//输出-84
std::cout << u+i << std::endl;//如果int占32位,输出4294967264

2.1.2 字面值常量

  1. 整型字面值具体的数据类型由它的值和符号决定,可以将整型写成十进制、八进制或十六进制。
    20 十进制
    024 八进制
    0x14 十六进制
  2. 默认的,浮点型字面值是一个double类型的值,表现为一个小数或科学计数法的指数形式。
  3. 字符和字符串字面值
    由单引号括起来的一个字符称为char型字面值
    字符串字面值的类型实际上是由字符常量构成的数组
    'a’ //字符字面值
    "Hello World“ //字符串字面值
  4. 注意:两类字符程序员不能直接使用:一类是不可打印字符,如退格或其他控制字符,因为他们没有可视的图符;另一类是在C++中有特殊含义的字符(单引号、双引号、问号、反斜线)。
  5. C++定义的转义序列包括:
    转义序列
  6. 泛化的转义序列:如\后面紧跟八进制数字(最多3个)或\x后面紧跟一个或多个十六进制数字。假设使用的是Latin-1字符集,以下是一些示例:
    在这里插入图片描述
  7. 指定字面值的类型
    在这里插入图片描述
  8. 注意:当使用一个长整型字面值时,请使用大写字母L来标记,因为小写字母l和数字1太容易混淆了。

2.2 变量

  1. 变量:具有类型、具有名称、可操作的存储空间。
  2. 类型决定了变量所需要的内存空间、布局方式、以及能够表示值的范围。
  3. 对于C++程序员来说,变量(variable)和对象(object)一般是可以互换的。

2.2.1 变量初始化

  1. 初始化
    作为C++新标准的一部分,使用花括号来初始化变量得到了全面应用。
int units_sold = 0;
int units_sold = {0};//列表初始化
int units_sold{0};//列表初始化 
int units_sold(0);

注意:当用于内置类型变量时,如果我们使用列表初始化,且初始值存在丢失信息的风险,则编译器将报错。

long double ld = 3.1415926536;
int a{ld},b={ld};//错误:转换未执行,因为存在丢失信息的风险
int c(ld),d=ld;//正确:转换执行,且确实丢失了部分值
  1. 默认初始化
    如果定义变量没有定义初始值,则变量被赋予默认值。默认值是由变量类型决定的,同时定义变量的位置也会有影响。
    a. 内置类型:由定义的位置决定,函数体之外初始化为0。
    b. 每个类各自决定其初始化对象的方式。
std:::string empty;//empty非显示地初始化一个空串
Sales_item item;//被默认初始化的Sales_item对象
  1. 注意:未初始化的变量含有一个不确定的值,将带来无法预计的后果,应该避免。
  2. 在同一条定义语句中,可以用先定义的变量值去初始化后定义的其他变量。
  3. 初始化是创建变量时赋予其一个初始值,而赋值是把对象的当前值擦除,以一个新值来替代。

2.2.2 变量的声明与定义

  1. 声明使得名字为程序所知,定义负责创建与名字关联的实体。
  2. 变量能且只能被定义一次,但是可以被多次声明。
  3. C++是一种静态类型语言。要求在使用某个变量之前必须先声明。
  4. 如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显式地初始化。
extern double pi = 3.14;//定义,不能放在函数体内部
int main(){
extern int i;//声明i而非定义i
int j;//声明并定义j
}

2.2.3 标识符

  1. C++标识符(identifier)由字母、数字和下划线组成,其中必须以字母或下划线开头。
  2. 标识符的长度没有限制,但对大小写敏感。
  3. 用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧连大写字母开头。
  4. 定义在函数体外的标识符不能以下划线开头。
  5. 标识符要能体现实际含义。
  6. 变量名一般用小写字母。
  7. 用户自定义的类名一般以大写字母开头。
  8. 如果标识符由多个单词组成,则单词之间应有明显区分。
  9. C++语言保留了一些名字供语言本身使用,这些名字不能作为标识符。
    在这里插入图片描述

2.2.4 名字的作用域

  1. C++中大多数作用域都以花括号分隔。
  2. 名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
  3. 如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。
  4. 建议:当第一次使用变量时再定义它。

2.3 复合类型

复合类型:是指基于其它类型定义的类型。

2.3.1 引用

  1. 引用:为一个已经存在的对象所起的另外一个名字。
  2. 定义引用时,把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用。
  3. 引用本身并不是对象,所以不能定义引用的引用。
  4. C++11中新增了“右值引用”,主要用于内置类;当我们使用术语“引用”时,一般指的是“左值引用”。
  5. 引用必须初始化,因为无法令引用重新绑定到另外一个对象。
  6. 引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起。
int ival = 1024;
int &refVal = ival;//refVal指向ival(是ival的另一个名字)
int &refVal2;//报错:引用必须初始化
refVal = 2;//把2给refVal指向的对象,此处即是赋给了ival
int li = refVal;//等同于li=ival
//正确:refVal13绑定到了那个与refVal绑定的对象上,即绑定了ival
int &refVal3 = refVal;

2.3.2 指针

  1. 指针(pointer):指向另外一种类型的复合类型。。
  2. 如果一条语句中定义了几个指针变量,每个变量前面都必须加上*符号。
  3. 和其他内置类型一样,在块作用域内定义指针如果没有初始化,将拥有一个不确定的值。
int *ip1, *ip2;//ip1和ip2都是指向int型对象的指针
double dp, *dp2;
  1. 可以使用取地址符(运算符&)获取指针所封装的地址:
int ival = 42;
int *p = &ival;//p是指向ival的指针
double *dp = &ival;//错误:类型不匹配
  1. 可以使用解引用符(运算符*)利用指针访问对象:
int ival = 42;
int *p = &ival;//p是指向ival的指针
std::cout<<*p ;//输出42
*p=0;
std::cout<<*p; //输出0
  1. 引用不是对象,不存在地址,所以不能定义指向引用的指针。
  2. 在声明语句中,&和*用于组成复合类型;在表达式中,它们的角色转换为运算符。含义截然不同。
  3. 指针和引用的不同:
    指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可以先后指向几个不同的对象。
    指针无须在定义时赋初值。
  4. 指针的值(即地址)应属于下列4种状态之一:
    指向一个对象。
    指向紧邻对象所占空间的下一个位置。
    空指针,意味着指针没有指向任何对象。
    无效指针,也就是上述情况之外的其他值。
  5. 解引用操作仅适用于那些确实指向了某个对象的有效指针。
  6. nullptr是一种特殊类型的字面值,可以被转换为任意其他的指针类型。
  7. 三种生成空指针的方法:
int *p = 0;	//直接将p初始化为字面常量0
int *p = nullptr;	//等价于int *p = 0
int *p = NULL;	//等价于int *p = 0,需要首先#include <cstdlib>
  1. 把int变量直接赋给指针是错误的,即使int变量的值恰好等于0也不行。
int zero = 0;
p1 = zero; //错误:类型不匹配
  1. 使用未经初始化的指针是引发运行时错误的一大原因。因此建议初始化所有指针,并且在可能的情况下,尽量等定义了对象之后再定义指向它的指针。
  2. 两个指针相等有三种可能:
    都为空。
    都指向同一个对象。
    都指向同一个对象的下一地址。
    注意:一个指针指向某对象,同时另一个指针指向另外对象的下一地址,此时也可能出现这两个指针值相同的情况,即指针相等。
  3. void* 可用于存放任意对象的地址,但不能直接操作void指针所指的对象,因为并不知道这个对象到底是什么类型。void的作用:
    拿它和别的指针比较。
    作为函数的输入或输出。
    赋给另外一个void*指针。
  4. 指向指针的指针
    通过*的个数可以区分指针的级别。
int ival = 1024;
int *pi = &ival;
int **ppi = &pi;//ppi指向一个int型的指针
  1. 指针的引用
    指针是对象,可以定义引用。
int i = 1024;
int *p;
int *&r = p; //r是一个对指针p的引用
r = &i;//r引用了一个指针,就是令p指向i
*r = 0;//解引用r得到i,也就是p指向的对象,将i的值改为0

面对比较复杂的指针或引用的声明语句,建议从右向左阅读。(如上例:离变量名r最近的符号是&,说明r是一个引用,声明符的其余部分说明r引用的类型为int *型的指针。)

2.4 const限定符

  1. 使用const对变量的类型加以限定,变量的值不能被改变。
const int bufSize = 512; //输入缓冲区大小
bufSize = 512; //错误:试图向const对象写值
  1. const对象一旦创建后其值就不能再改变,所以const对象必须初始化(其他时候不能出现在等号左边)。
const int i = get_size(); //正确:运行时初始化
const int j = 42; //正确:编译时初始化
const int k; //错误:k是一个未经初始化的常量
const int bb = 0;
void * a= bb;//在编译的时候,会把bb变成常量
  1. 默认状态下,const对象仅在文件内有效。如果想在多个文件之间共享const对象,必须在变量的定义和声明之前添加extern关键字。
//file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//file_1.h头文件
extern const int bufSize;

2.4.1 const的引用

  1. 与普通引用不同的是,常量引用(把引用绑定到const对象上,是对const的引用)不能被用作修改它所绑定的对象。
  2. 在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。
  3. 常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未做限定。
const int ci = 1024;
const int &r1 = ci; //正确:引用及其绑定的对象都是常量
r1 = 42;//错误,相当于ci=42,试图修改常量
int &r2 = ci;//错误:ci是常量,存在通过r2改变ci(const)的风险

int i = 42;
// 允许将const int &绑定到一个普通int对象上
const int &r1 = i; //正确:i依然可以通过其他途径修改
const int &r2 = 42;//正确,r2是一个常量引用
const int &r3 = r1*2;//正确,r3是一个常量引用
int &r4 = r1*2; //错误:不能通过一个非常量的引用指向一个常量

double dval = 3.14;
const int &ri = dval; //允许
int &ri = dval; //错误:因为改变的是编译器生成的中间值

2.4.2 指针和const

  1. 指向常量的指针不能用于改变其所指对象的值。
  2. 允许令一个指向常量的指针指向一个非常量对象。
  3. 所谓指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
const double pi = 3.14;
double *ptr = &pi; //错误:存在通过ptr指针修改pi的风险
const double * cptr = &pi;
*cptr = 42; //错误

double dval = 3.14;
cptr = &dval; //正确:但不能通过cptr修改dval的值
  1. const指针:指针是对象,也可以限定为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。
  2. 把*放在const关键字之前,用以说明指针是一个常量,即不变的是指针本身的值,而非指向的那个值。
int errNumb = 0;
//curErr指向一个一般的非常量整数,可以用curErr去修改errNumb的值。
int *const curErr = &errNumb; //curErr将一直指向errNumb

从右向左阅读:离curErr最近的符号是const,意味着curErr本身是一个常量对象,声明符中的下一个符号是*,意思是curErr是一个常量指针,最后该常量指针指向了一个int对象。

if(*curErr){
errorHandler();
*curErr = 0; //正确:试图修改变量errNumb
}
const double pi = 3.14159;
const double *const pip = &pi;//指向常量的常量指针
*pip = 2.71;//错误: 试图修改常量pi

pip是一个指向常量的常量指针,不论是pip所指的对象值还是pip自己存储的那个地址都不能改变。

2.4.3 顶层const

  1. 顶层const:表示指针本身是个常量。
  2. 底层const:表示指针所指的对象是一个常量。
  3. 一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,底层const则与指针和引用等复合类型的基本类型部分有关。指针类型既可以是顶层const也可以是底层const。
  4. 用于声明引用的const都是底层const。
int i = 0;
int *const p1 = &i; //顶层
const int ci = 42; //顶层
const int *p2 = &ci;//底层
const int *const p3 = p2;//(左:底层 ), (右:顶层)

i = ci;//正确
p2 = p3;//正确

int *p = p3;//错误:存在通过*p修改*p3(const)的风险
p2 = &i;  //正确:只是不能通过p2修改i而已
int &r = ci; //错误:存在通过r修改ci(const)的风险
const int &r2 = i; //正确:只是不能通过r2修改i而已

2.4.4 constexpr和常量表达式

  1. 常量表达式:值不会改变并且在编译过程就能得到计算结果的表达式。
const int max_files = 20; //是
const int limit = max_files +1; //是
int staff_size = 27; //不是
//尽管sz本身是一个常量,但它的具体值直到运行时才能获取到
const int sz = get_size(); //不是
  1. C++11标准规定,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式。
  2. 声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
  3. 需要在编译时就得到计算,声明constexpr时用到的类型必须显而易见,容易得到(称为:字面值类型)。
  4. 自定义类型(例如:Sales_item)、IO库、string等类型不能被定义为constexpr。
constexpr int mf = 20;
constexpr int limit = mf +1;
constexpr int sz = size(); //只有当size是一个constexpr函数时才正确
  1. 一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
  2. 在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关。
const int *p = nullptr//p是一个指向整型常量的指针
constexpr int *q = nullptr//q是一个指向整数的常量指针(constexpr把它所定义的对象置为了顶层const)
int j = 0;
constexpr int i = 42;....
//i和j必须定义在函数之外
constexpr const int *p = &i; //p是常量指针,指向常量
constexpr int *p1 = &j; //p1是常量指针,指向变量j

2.5 处理类型

2.5.1 类型别名

提高可读性

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

using SI = Sales_item; //C++11,别名声明
wages hourly, weekly; 
SI item; //等价于Sales_item item

对于指针这样的复合类型,类型别名的使用可能会产生意想不到的结果:

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

const char *cstr = 0; //是对const pstring cstr =0 的错误理解

2.5.2 auto类型说明符

  1. auto(C++11)让编译器分析表达式所属的类型。
  2. auto定义的变量必须有初始值。
  3. 使用auto能在一条语句中声明多个变量,但该语句中所有变量的初始基本数据类型都必须一样。
  4. auto一般会忽略掉顶层const,同时底层const则会保留下来。
  5. 设置一个类型为auto的引用时,初始值中的顶层常量属性仍然保留。
auto item = val1 + val2; 

auto i =0, *p = &i; //正确
auto sz = 0, pi = 3.14; //错误:auto已经被推断为int型,却需要被推断为double型
int i = 0, &r = i;
auto a = r; //a是int型

const int ci = i, &cr = ci;
auto b = ci; //b是int型,ci的顶层const被忽略
auto c = cr; //c是int型,ci的顶层const被忽略
auto d = &i; //d是整形指针,整数的地址就是指向整形的指针
auto e = &ci; //e是指向整数常量的指针(底层const没有被忽略)

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

auto &g = ci; //g是一个整形常量引用,绑定到ci,(底层const没有被忽略)

auto &h = 42; //错误:不能为非常量引用绑定字面值
const auto &j =42; //正确:可以为常量引用绑定字面值

auto k = ci, &l = i; 
auto &m = ci, *p = &ci;
auto &n = i, *p2 = &ci; //错误:类型不一致

2.5.3 decltype类型指示符

  1. decltype(C++11)选择并返回操作数的数据类型,不要其值。
  2. 引用从来都作为其所指对象的同义词出现,只有用在decltype处是一个例外。
  3. 如果表达式的内容是解引用操作,则decltype将得到引用类型。
  4. decltype的结果类型与表达式形式密切相关。
  5. 切记:decltype((v))(注意是双层括号)的结果永远是引用,v本身也可以是引用。而decltype(v)结果只有当v本身就是一个引用时才是引用。
decltype(f()) sum = x;// sum的类型就是函数f返回的类型

const int ci = 0, &cj = ci;
decltype(ci) x = 0; // x的类型是const int
decltype(cj) y = x; // y的类型是const int &
decltype(cj) z; //错误:z是一个引用,必须初始化
int i = 42, *p = &i, &r = i;
decltype(r+0) b; //正确:b为int型

//注意:下面的*不是出现在声明中,而是表达式中的解引用运算符
decltype(*p) c; //错误:解引用表达式,c的类型为引用,需要初始化

//变量如果是加上括号, decltype的结果将是引用
decltype( (i) ) d; //错误:d是int&类型,必须初始化
decltype( ( (i) ) ) d1 = i; //正确:d1是int&类型,绑定为了i
decltype( i ) e; //正确:e是一个(未初始化的)int

2.6 自定义数据结构

  1. 数据结构:把一组相关的数据元素组织起来然后使用它们的策略和方法。
  2. 类定义:可以使用关键字class或struct,二者默认的继承、访问权限不同
    struct是public的,class是private的。
  3. 数据成员定义了类的对象的具体内容,每个对象有自己的一份拷贝。
struct Sales_data{
std::string bookNo;
//类内初始值(C++11):创建对象时,类内初始值将用于初始化数据成员。
unsigned units_sold = 0; 
double revenue = 0.0;
};//类定义的最后需要加上分号
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值