繁书简读之C++ Primer Day2: 变量和基本类型

2.1 基本内置类型

基本内置类型包含算术类型和空类型。算术类型有包括字符/整数/浮点数/bool类型

2.1.1 算术类型

类型

含义

最小位宽

bool

布尔类型

未定义

char

字符类型

8bit

wchar_t

宽字符

16bit

char16_t

Unicode字符

16bit

char32_t

Unicode字符

32bit

short

短整型

16bit

int

整形

16bit(在32/64位机器上是32bit)

long

长整型

32bit

long long

长整型

64bit

float

单精度浮点型

6位有效数字

double

双精度浮点型

10位有效数字

long double

扩展精度浮点型

10位有效数字


1. bool类型的取值是true或false

2. int/short/long都带有符号,char是否带符号与编译器有关,建议使用signed char或unsigned char

3. 无符号类型只能表达非负数

4. Cpp提供了多种字符类型:

a) char:一个char空间应确保可以存放机器基本字符集中任意字符对应的数值,即1个char=1个机器字节

b) wchar_t:宽字符,用于扩展字符集,wchar_t确保可以存放机器最大扩展字符集中的任意一个字符

c) char16_t & char32_t:为Unicode字符集服务

5. 类型选择建议:

a) 确定数据为非负数时,选用unsigned类型

b) 整数运算用int,数值太大时用long long

c) 算术表达式不要使用char和bool类型

d) 浮点运算用double,因为在计算机上float和double的计算代价相差不多

2.1.2 类型转换

1. 原则:

a) 把非bool型的算术值赋给bool型,初始值为0则结果为false,否则结果为true

b) 把bool型赋给非bool型时,初始值为false则结果为0,否则结果为1

c) 把浮点数赋给整数类型时,结果值仅保留浮点数中的整数部分

d) 把整数值赋给浮点类型时,小数部分记为0。如果整数所占空间超过了浮点型的容量,可能带来精度损失

e) 赋给无符号类型一个超出它表示范围的值时,结果是初始值对无符号类型表示数值总数(例如8bit能表示的数值是0-255,即总数是256)取模后的余数

f) 赋给有符号类型一个超出范围的值,属于undefined behavior

g) 程序尽量避免依赖于实现环境的行为,例如int的尺寸在不同环境可能不同

2. 表达式中既有无符号数又有有符号数时,有符号数会被转换成无符号数

3. 无符号数不会小于0这一事实关系到循环的写法

for (unsigned u = 10; u >= 0; --u)
std::cout << u << std::endl; //此处for循环条件将永远成立

2.1.3 字面值常量

1. 整形和浮点型字面值

a) 整形字面值中以0开头的代表八进制数,以0x或0X开头的代表十六进制数,以0b或者0B开头的为二进制数。C++14新增了单引号'形式的数字分隔符。数字分隔符不会影响数字的值,但可以通过分隔符将数字分组,使数值读写更容易。

std::cout << 0B1'101;   // 输出"13"
std::cout << 1'100'000; // 输出"1100000"

b) 默认情况十进制字面值为有符号型

c) 浮点型字面值默认时double型,可以用小数或者科学计数法表示,科学计数法中的指数部分用E或e标识

1.2 .003 1.23E2 0e0

2. 字符和字符串字面值

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

b) 字符串字面值的类型是由常量字符构成的数组(array)。编译器在每个字符串的结尾处添加一个空字符'',因此字符串字面值的实际长度要比它的内容多一位,这里需要注意常用函数strlen和运算符sizeof的区别,对于字符数组,strlen计算的是不包含‘‘的长度,而sizeof计算的是包含’‘的长度

3. 转义序列

含义

转义字符

newline

horizontal tab

alert (bell)

a

vertical tab

v

backspace

b

double quote

"

backslash

question mark

?

single quote

'

carriage return

r

formfeed

f

4. 泛化转义序列:形式是x后紧跟1个或多个十六进制数字,或者后紧跟1个、2个或3个八进制数字,其中数字部分表示字符对应的数值。如果后面跟着的八进制数字超过3个,则只有前3个数字与构成转义序列;相反,x要用到后面跟着的所有数字。


12
40
M
115

5. bool字面值和指针字面值

true false
nullptr

2.2 变量

变量提供一个具名的、可供程序操作的存储空间。 C++中变量和对象一般可以互换使用,对象通常指一块能存储数据并具有某种类型的内存

2.2.1 变量定义

1. 形式:类型说明符(type specifier) + 一个或多个变量名组成的列表。如int a = 0, b;

2. 初始化:对象在创建时获得了一个特定的值。

a) 初始化不等于赋值,而是创建变量并赋予一个初值

b) 用花括号初始化变量成为列表初始化(list initialization),当用于内置类型的变量时,使用列表初始化且初始值存在信息丢失风险时,编译器会报错

3. 默认初始化:

a) 对于内置类型,定义于任何函数体之外的变量被初始化为0,函数体内部的变量将不被初始化(uninitialized)

b) 定义于函数体内的内置类型对象如果没有初始化,则其值未定义,使用该类值是一种错误的编程行为且很难调试。类的对象如果没有显式初始化,则其值由类确定

c) 建议初始化每一个内置类型的变量

2.2.2 变量声明和定义的关系

1. 为了支持分离式编译,C++将声明和定义分开,声明(declaration)使得名字为程序所知,定义(definition)负责创建与名字相关联的实体;还有一种说法,声明并没有分配内存,而定义则是为变量分配了内存

2. 只声明而不定义:在变量名前添加关键字 extern,如extern int a; 但如果包含了初始值,就变成了定义:extern int b = 3;

3. 变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明

2.2.3 标识符:

字母数字下划线开头,大小写敏感,具体命名规范:

1. 标识符尽量体现实际含义

2. 变量名一般小写

3. 用户自定义的类类型一般以大写字母开头

4. 包含多个单词的标识符,使用驼峰命名法

2.2.4 名字的作用域

1. 定义在函数体之外的名字拥有全局作用域(global scope)。声明之后,该名字在整个程序范围内都可使用

2. 变量最好第一次使用变量时再定义它

3. 嵌套的作用域

a) 同时存在全局和局部变量时,已定义局部变量的作用域中可用::var显式访问全局变量var。

b) 但是用到全局变量时,尽量不使用重名的局部变量

2.3 复合类型

复合类型就是基于其他类型定义的类型,引用和指针是其中两种

2.3.1 引用

1. 引用为对象起了另外一个名字,引用类型引用(refers to)另外一种类型,通过将声明符写成&d的形式来定义引用类型,其中d是变量名称。初始化引用时,是将引用和对象绑在一起。

2. 特点:

a) 引用不是对象,只是对象的别名

b) 引用的类型要与之绑定的对象严格匹配

c) 引用无法重定向

d) 引用必须初始化

e) 引用只能绑定对象,不能是字面值或者表达式

2.3.2 指针

1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在生命周期内它可以先后指向不同的对象

2. 通过将声明符写成*var的形式来定义指针类型,其中var是变量名称。如果在一条语句中定义了多个指针变量,则每个量前都必须有符号*

int *p1, *p2

3. 特点:

a) 是实实在在的对象

b) 不必要初始化,但建议初始化所有指针

c) 可以重定向

d) 可以有二级指针,即指向指针的指针,而不能有引用的引用

4. 不能定义指向引用的指针,因为引用不是对象,没有实际地址,可以定义指向指针的引用

5. 复杂写法

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

面对上述复杂写法,从右向左读更容易理解,首先r是一个引用,它引用的类型是一个int *的类型

6. 空指针

int *p = nullptr;
int *p = 0;
int *p = NULL; //以上三种写法都可以用来定义空指针

7. void *指针:注意void *指针和空指针是两回事,void *代表可以存放任意对象的地址,实际工程应用中一般仅在形参中用void *传导,到具体函数执行时需要把void *指针强制转换为特定类型

8. 两个指针相减类型是ptrdiff_t

2.3.3 理解复合类型的声明

1. 指向指针的指针

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

2. 指向指针的引用(References to Pointers)

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

2.4 const限定符

1. const对象必须初始化,一旦定义就不能再改变值

2. 默认情况下,const对象仅在当前文件内有效,类似于C语言用static关键字定义的变量或函数,当多个文件定义了多个同名const变量时,各自在各自文件中有效

3. 在多个文件间共享const对象

a) 编译期确定的const对象,应该定义在头文件中,其他源文件包含该头文件,不会造成重复定义

b) 运行时才能确定的const对象,应该在头文件中声明,源文件中定义,此时const对象的声明和定义前都该加上extern关键字

//in file1.cpp
extern const int var = func();
//in file1.hpp
extern const int var;

2.4.1 const引用

1. const引用也叫常量引用,是对常量的引用,引用必须初始化,所以const引用也必须初始化

2. 笔者觉得有必要强调一下,引用不是对象,因此const引用不是说引用是常量,而是说const引用不能改变其绑定的对象了

cont int a = 1;
const int &ra = a;
ra = 2; 									//编译器会报错,因为ra是对常量的引用
int &rb = a; 					//编译器也会报错

3. 笔者在前文已经介绍过引用的类型要和绑定的对象严格匹配,但是在const引用这里有两个例外:

a) 初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可

int a = 10;
const int &r1 = a; 							//ok
const int &r2 = 20; 					//ok
const int &r3 = r1 * 2; 		//ok
int &r4 = r1 * 2; 							//error

b) 允许为一个const引用绑定非常量的对象/字面值或一般表达式

double var = 1.23;
const int &rvar = val; 		//ok

4. 可以把非常量对象绑定到常量引用上,但是不能把常量绑定到普通非常量引用上

2.4.2 指针和const

这里有两个经常容易混淆的概念,即指针常量和常量指针。这里笔者的观点跟<>第五版中相关章节的描述正好相反。

1. 常量指针:即指向常量的指针(*放在const右边),它代表指针所指向的对象是个常量,其值不能被改变

const int a = 1;
int *pa = &a; 						//error
const int *q = &a;
*q = 2; 										//error
int b = 2;
q = &b; 										//ok,但这里如果尝试通过q改变b的值是非法的

2. 指针常量:又叫常指针(*放在const左边),首先它是个常量,即指针本身是个常量,所以指针本身不能改变指向,但其指向的值却可以改变

int b = 0;
int *const pb = &b; //pb指向了b的地址,且不能再改变指向

3. 指向常量的常指针

const double a = 3;
const double *const pa = &a;

4. 这里综合笔者读书时所学以及网上查到的相关资料总结,可以确认笔者的上述表述是对的,而原书中的表述可能会造成误解,请读者予以重点关注

2.4.3 顶层const

1. 顶层const表示对象本身是个常量,底层const表示指针或引用所指向的对象是一个常量。

2. 注意:顶层const对任何数据类型都通用,但是底层const只用于引用和指针,再进一步,引用只有底层const

3. 区分顶层const和底层const:按照表达式的定义从右向左读,顶层const再右边,底层const在左边

const int &const ra = a; //error,这里出现了顶层const的引用

4. 执行拷贝操作时,顶层const会被忽略,然而底层const却可不可以忽略

2.4.4 constexpr和常量表达式

1. 常量表达式(constant expressions)指值不会改变并且在编译过程就能得到计算结果的表达式, C++11允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式

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

3. 字面值属于常量表达式,由常量表达式初始化的const对象也是常量表达式

4. 注意:如果用constexptr定义指针,则限定符仅对指针有效,与指针所指对象无关,即constexpr定义的对象具有顶层const属性

5. const和constexpr异同:

a) 二者限定的都是常量

b) constexpr对象的值必须在编译期间确定,而const对象的值可以在编译器也可以在运行时确定

c) constexpr变量是真正的“常量“,而const现在更多表示“只读“

6. 建议:

a) 如果认定一个变量是常量表达式,就把它声明为constexpr类型

2.5 处理类型

2.5.1 类型别名

1. 传统类型别名(尤其在C语言开发中)常用typedef来定义

typedef double wages; // wages是double的别名
typedef wages base, *p; // base是double的别名, p是double *的别名

2. C++11使用关键字using进行别名声明

using wages = double;

3. 指针/常量和类型别名

typedef char *pstring;
const pstring cstr = 0; //注意:此处cstr是一个指向char类型的指针常量(即指针本身是常量,不能改变其指向),不能直接将其按替换的方式理解为const char* cstr = 0(错误),错误的理解方式cstr就变成了常量指针

2.5.2 auto类型说明符(C++11开始)

1. auto说明符让编译器自动分析表达式所属的类型,auto定义的变量必须有初始值

2. 一条声明语句只能有一个数据类型,所以当一个auto声明多个变量时,多个变量必须是同一个数据类型

3. 复合类型,常量和auto:

a) 编译器推断的auto类型有时和初始值并不一样,编译器会做调整

b) auto根据引用来推断类型时会以引用对象的类型作为auto的类型

c) auto一般会忽略顶层const,因此对于非指针类型的常量对象,auto推断的结果不含const;若希望auto推断出是顶层const,需要明确指出

d) auto会保留底层const

e) 一言以蔽之:auto会忽略引用与顶层const

const int ci = 10, cr = ci;
auto b = ci; //b是普通int
auto c = cr; //c是普通int
const auto d = ci; //d是const int
auto &e = ci; //e是常量引用(常量引用是底层const)
auto f = &ci; //f是const int *(因为ci只读,指向它的指针是常量指针,具有底层const属性)

f) int与int */int &是一个基本数据类型,而const int与int不是一种类型

g) 用auto定义引用时,必须要用&明确标识,否则会被忽略

2.5.3 decltype类型指示符(C++11新增)

1. 从表达式的类型推断出要定义的变量类型,注意此过程编译器不会计算表达式的值

2. 与auto不同,如果decltype使用的表达式是一个变量,则它返回该变量的类型(包括顶层const和引用)

const int ci = 0, &cj = ci;
decltype(ci) x = 0; 			// x 是const int
decltype(cj) y = x; 			// y 是const int &类型
decltype(cj) z;						 // error: ,z是const int &类型,但未初始化

3. decltype和引用:

a) 若decltype使用的表达式不是变量,则返回表达式结果对应的类型,可以用这种方式来确保不获取到引用类型

b) 注意,解引用指针的结果是一个引用类型给变量加括号的结果也是引用类型(因为变量是一种可以作为赋值语句左值的特殊表达式),赋值操作的结果也是引用类型

int a = 10, &r=a, *p;
decltype(r + 0) b; 						//b类型是int
decltype(*p) c = a; 				//c类型是int &
decltype((a)) d = a; 				//d类型是int &

2.6 自定义数据结构

2.6.1 定义sales_data类型

1. 格式:struct/class + 类名 + 类实体 + 分号,类实体可以为空

2. C++11规定可以为类的数据成员提供一个类内初始值,创建对象时,类内初始值将用于初始化数据成员,没有初始值的成员将被默认初始化

a) 类内初始值可以使用花括号或放在等号右边,不能使用圆括号

b) 类定义最后要加分号

2.6.2 使用sales_data类型

2.6.3 编写自己的头文件

1. 头文件通常定义只能被定义一次的实体,比如类,const和constexpr对象

2. 确保头文件多次包含但不报重复定义的技术叫预处理器

3. 头文件保护符(header guard)依赖于预处理变量,预处理变量一般大写,预处理变量有两种形态:已定义和未定义

4. C++包含3个头文件保护符:

a) #ifndef,一旦为真,则执行后续操作到#endif为止

b) #define,把一个名字设定为预处理变量

c) #endif

#ifndef __SALES_DATA_H__
#define __SALES_DATA_H__
…
…
…
#endif

也可以使用#pragma once来防止头文件重复包含

展开阅读全文

页面更新:2024-03-11

标签:变量   类型   书简   常量   表达式   指针   底层   符号   字符   定义   对象

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top