c++笔记之变量和基本类型
问题
- 指针和引用有4点不同,分别是哪些?
- const 对象必须怎样
- const 对象的作用范围
- 什么是常量引用,如何声明,是顶层还是底层
- 常量引用与常量对象、非常量对象的关系。
- 什么是常量指针,如何声明,是顶层还是底层
- 常量指针与常量对象、非常量对象的关系。
- 顶层 const 和底层 const 都是什么,在什么位置
- 如何区分顶层 const 和底层 const
- constexpr 是什么,特点是什么
回答
- 指针是对象而引用不是;指针可以重定向引用不可以;有指向指针的指针无引用的引用;引用必须初始化指针不需要
- 必须初始化
- 默认范围是文件内
- 不能改变对象的引用是常量引用,const int& i = a,是底层 const
- 不能用非常量引用绑定常量对象,可以用常量引用绑定非常量对象。
- 常量指针表明指针是个常量,其内存储的地址不能改变,但是指针还能修改所指对象的值。int* const p = a,是顶层const。
- 可以用常量指针指向非常量对象。
- 顶层 const 表示指针本身是常量,底层 const 表示所指对象是常量。顶层 const 在右边,底层 const 在左边
- 只有指针同时有顶层和底层,const 在星号右边是顶层,左边是底层。引用的 const 是底层,其他类型 const 是顶层。
- 常量表达式。两个点:值不能改变、在编译阶段就可以计算出值
问题
- 浮点数赋给整型变量时如何舍入?
- decltype 是什么,如何使用
- 如何声明而非定义一个变量
- 如果指针不初始化会有什么影响
- 如何在多个文件间共享 const 对象
- 使用 auto 来定义引用时要注意什么
- 预处理变量的作用范围是什么
- C++属于静态类型语言,静态类型语言的含义是什么?
- C++有两种定义类型别名的方式,分别是什么
回答
- 只保留小数点前的部分,即向零舍入
- 用来获取变量类型,decltype(c) a;
- 使用 extern 修饰符: extern int i:
- 在块作用域中,未初始化的指针的值是未定义的。
- 如果想在多个文件间共享const对象,必须在变量的定义前添加extern关键字并在本文件中声明。声明和定义都要加extern
- 用 auto 定义引用时,必须要加 & 符号。尤其是在范围 for 循环中,当想要修改值时,一定要记得加上引用符。
- 文件内。
- 静态类型语言在编译时检查变量类型。
- typedef unsigned int size_type 和 using size_type = unsigned int;
第2章 变量和基本类型
C++定义了几种基本内置类型,如字符、整型、浮点数等。
2.1 基本内置类型
基本内置类型包括算数类型和空类型。算数类型包括字符、整型数、浮点数和布尔值。
2.1.1 算术类型
| c++算数类型 | |||
|---|---|---|---|
| 类型 | 含义 | 最小尺寸 | 说明 |
| bool | 布尔类型 | 未定义 | 取值为真(true)或假(false) |
| char | 字符 | 8位 | 1个 char 的空间应确保可以存放机器基本字符集中任意字符对应的数字值 |
| wchar_t | 宽字符 | 16位 | 用于扩展字符集,确保可以存放机器最大扩展字符集中的任意一个字符 |
| char16_t | Unicode字符 | 16位 | 用于扩展字符集,为 Unicode 字符集服务 |
| char32_t | Unicode字符 | 32位 | 用于扩展字符集,为 Unicode 字符集服务 |
| short | 短整型 | 16位 | |
| int | 整型 | 16位 | 一个 int 至少和 一个 short 一样大 |
| long | 长整形 | 32位 | 一个 long 至少和一个 int 一样大 |
| long long | 长长整形 | 64位 | C++11 中新定义,一个 long long 至少和 一个 long 一样大 |
| float | 浮点型 | 6位有效数字 | |
| double | 双精度浮点型 | 10位有效数字 | |
| long double | 扩展精度浮点型 | 10位有效数字 | 常常用于有特殊浮点需求的硬件 |
计算机以比特序列存储数据,每个比特非0即1,
可寻址的最小内存块称为“字节(byte)”,内存的基本单元称为“字(word)”
大多数机器的字节由8比特构成,字则由32或64比特构成
- 无符号类型
int、short、long和long long都是带符号的,前面加上unsigned就可以得到无符号类型,例如unsigned long
unsigned int可以缩写成unsigned。
char比较特殊,类型分为三种:char、signed char、unsigned char
char是signed char或unsigned char的其中一种(编译器决定)
c++字符类型选择:
明确知晓数值不可能为负时,选用无符号类型。
整数运算用 int,数值太大时用 long long,不用 short 和 long
浮点数运算用 double。float 和 double 的计算代价相差无几
2.1.2 类型转换
几种类型转换的情况:
- 把浮点数赋给整型时,结果仅保留小数点前的部分。
- 赋给无符号类型超出范围的值时,结果是初始值对无符号类型表示数值总数取模后的余数。比如 -1 赋给 8 位 unsigned char 的结果是 255(-1=256*(-1)+255)
- 赋给带符号类型超出范围的值时,结果是未定义的。程序可能工作,可能崩溃。
1 | bool b=42; //b为真 |
- 程序尽量避免依赖于实现环境的行为。比如 int 的尺寸在不同环境可能不同。
- 含有无符号类型的表达式
- 个表达式中既有无符号数又有int值时,int会被转换成无符号数。
- 无符号减无符号数,结果还是无符号数,如果是负值就等于该符数加上无符号数的模,切勿混用带符号类型和无符号类型。
1 | unsigned u=10; |
2.1.3 字面值常量
一个型如42的值就称为字面值常量(literal)
整型和浮点型字面值
整型字面值中 0 开头的整数是 8 进制,0x 开头的整数是十六进制。
整型字面值的具体数据类型由它的值和符号决定。默认情况下十进制字面值是带符号数,类型是 int, long, long long 中能容纳当前值的尺寸最小的那个。
浮点型字面值可以用小数或科学计数法表示,科学计数法中的指数部分用 E 或 e 标识。
1 | 3.14159 3.14169E0 0. 0e0 .001 |
字符和字符串字面值
单引号括起来的一个字符是 char 型字面值,双引号括起来的 0 个或多个字符则构成字符串型字面值。
字符串字面值的类型实际上是字符数组,编译器会向每个字符串结尾添加一个空字符(’\0’),因此字符串字面值的实际长度要比它的内容多 1。如 “A” 代表了一个长度为 2 的字符数组。
如果两个字符串字面值紧邻且仅由空格、缩进和换行符分隔,则它们实际上是一个整体。因此字符串比较长时可以直接分行书写。
“A is B” “and B is A”; //两个字符串实际上是一个整体。
转义序列
C++ 定义的转义序列包括:

在程序中,上述转义序列被当作一个字符使用。
也可以使用泛化的转移序列,形式是 \后跟 1~3 个八进制数字或 \x 后跟 1 个或多个十六进制数字。
指定字面值的类型
可以通过给字面值增加前缀和后缀来改变字面值的默认类型。

注意 12f 是错的,不能给整型字面值加 f 后缀,可以使用 12.f。
2.2 变量
对于c++而言,”变量“和”对象“一般可以互换使用。
c++中,对象通常指一块能存储数据并具有某种类型的内存
2.2.1 变量定义
● 变量提供一个具有名称的、可供程序操作的存储空间。
● 变量都具有数据类型,通过数据类型可以决定变量所需要的内存空间、布局方式、以及能够表示值的范围
1 | int sum=0,value,//sum、value和unit_sold都是int |
注:对于我们自己定义的类型item,我们希望自己定义的类型和它内置的类型用法上是接近的。最好是一样,这样我们的学习成本比较低可以直接拿过来使用。
初始化
可以在同一条定义语句中使用先定义的变量去初始化后定义的其他变量。
初始化不是赋值,初始化是创建变量时赋予一个初始值,赋值是把对象的当前值擦除并用一个新值来替代。
下面四种初始化方式都是可行的,其中使用花括号的方式叫做列表初始化。
1 | int units_sold = 0; |
当用于内置类型的变量时,使用列表初始化且初始值存在信息丢失的风险,编译器会报错。
1 | long double ld = 3.1415926536; |
默认初始化
● 如果定义变量没有定义初始值,则变量被赋予默认值。
● 默认值是由变量类型决定的,同时定义变量的位置也会有影响。
● 内置类型:由定义的位置决定,函数体之外初始化为0,函数体内不初始化。
● 每个类各种决定其初始化对象的方式
类的对象如果没有显式地初始化,则其由类确定。string 默认初始化为一个空串。
1 | std:::string empty;//empty非现实地初始化一个空串 |
未初始化的变量含有一个不确定的值,将带来无法预计的后果,应该避免。建议初始化每一个内置类型的变量。
2.2.2 变量声明和定义的关系
Q:变量声明和定义的区别
A:为变量分配地址和存储空间的称为定义,不分配地址的称为声明。一个变量可以在多个地方声明,但是只在一个地方定义。加入 extern 修饰的是变量的声明,说明此变量将在文件以外或在文件后面部分定义。
说明:很多时候一个变量,只是声明不分配内存空间,直到具体使用时才初始化,分配内存空间,如外部变量。
c++是一种静态类型语言,其含义是在编译阶段检查类型。这就要求我们在使用某个变量之前必须先声明。
如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显示的初始化,任何包含了显式初始化的声明即成为定义。声明变量不能赋值。例:extern int i = 1; // 定义 i,初始化抵消了 extern 的作用。
extern不是定义,是引入(声明)在其它源文件中定义的非static全局变量
1 | extern double pi = 3.14;//定义,不能放在函数体内部 |
变量只能被定义一次,但是可以多次声明。
2.2.3 标识符
标识符组成:字母、数字、下划线。不能以数字开头,对大小写敏感。标识符的长度没有限制。
名字的作用域
同一个名字如果出现在程序的不同位置,也可能指向不同的实体。
C++中大多数作用域都以花括号分隔。
名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。

如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。
2.3 复合类型
复合类型就是基于其他类型定义的类型,引用和指针是其中两种。
2.3.1 引用
引用是为对象起的另一个名字定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用引用本身并不是对象,所以不能定义引用的引用
1 | int ival = 1024; |
引用不是对象,不存在地址,所以不能定义指向引用的指针。
引用必须初始化。引用的初始值必须是一个对象,不能是字面值。
对引用的所有操作都是对与之绑定的对象的操作。
引用只能绑定在对象上,不能与字面值或表达式绑定。
引用只能绑定同类型对象。
2.3.2 指针
指针:对地址的封装,本身就是一个对象
●定义指针类型的方法是将声明符写成d的形式
●如果一条语句中定义了几个指针变量,每个变量前面都必须加上符号
●和其他内置类型一样,在块作用域内定义指针如果没有初始化,将拥有一个不确定的值
指针必须指向指定的类型,不能指向其他类型。
1 | int *ip1, *ip2;//ip1和ip2都是指向int型对象的指针 |
指针与引用的不同:
- 指针是一个对象而引用不是;
- 指针可以重定向引用不可以;
- 有指向指针的指针无引用的引用;
- 指针不需要在定义时赋初值而引用需要。
- 不能定义指向引用的指针。可以定义指向指针的引用。
int p; int &r = p; // r是对指针p的引用
面对如上 *&r 这样比较复杂的指针或引用的声明语句时,从右向左读比较易于弄清。
可以使用取地址符(操作符&)获取指针所封装的地址:
1 | int ival = 42; |
可以使用解引用符(操作符*)利用指针访问对象
1 | int ival = 42; |
空指针(null pointer)不指向任何对象
在使用一个指针之前,可以首先检查它是否为空。
1 | int *p1 = nullptr; //C++11(最好使用这种方式), NULL 是在头文件 cstdlib 中定义的预处理变量,值为 0 |
1 | int zero = 0; |
建议初始化所有指针。
非零指针对应的条件值是 ture,零指针对应的条件值是 false。
void*指针
纯粹的地址封装,与类型无关。可以用于存放任意对象的地址。
void* 指针和空指针不是一回事。
1 | double obj = 3.14, *pd = &obj; |
指向指针的指针
通过*的个数可以区分指针的级别
1 | int ival = 1024; |
指向指针的引用
指针是对象,可以定义引用。
1 | int i = 1024; |
2.3.3 理解复合类型的声明
定义复合类型的变量要比定义基本类型的变量复杂很多。
一条声明语句是由一个基本数据类型和紧随其后的声明符列表组成的。
引用符 & 和指针符 * 都是类型说明符,类型说明符是声明符的一部分。
int &a=b, &c=b; int *a=nullptr, b=1;
2.4 const限定符
把变量定义成一个常量
使用const对变量的类型加以限定,变量的值不能被改变
1 | const int bufSize = 512; //输入缓冲区大小 |
const 对象必须初始化,因为一旦创建就不能再改变值。(其他时候不能出现在等号左边)
1 | const int i = get_size(); //正确:运行时初始化 |
1 | const int bb=0; |
默认情况下,const 对象仅在文件内有效。如果想在多个文件间共享 const 对象,必须在变量的定义前添加 extern 关键字并在本文件中声明。声明和定义都要加 extern
1 | //file_1.cc定义并初始化了一个常量,该常量能被其他文件访问 |
2.4.1 const的引用
const 的引用:常量引用。
1 | const int ci = 1024; |
1 | int i = 42; |
对象不必是常量。对 const 对象的引用也必须是常量。
引用必须初始化,因此常量引用也必须初始化。
注意引用不是对象,因此常量引用不是说引用是常量,引用本来就只能绑定一个对象,而是引用不能改变引用的对象了。
引用的类型必须与其所引用对象的类型一致,但是有两个例外。其中一个例外就是初始化常量引用时允许用任意表达式作为初始值(包括常量表达式),只要该表达式结果可以转换为引用的类型。
const int &r = 42; // 常量引用可以绑定字面值
当用常量引用绑定一个非常量对象时,不能通过引用改变引用对象的值,但是可以通过其他方式改变值。常量指针也一样。
2.4.2 指针和const
指向常量的指针的用法和常量引用相似,但是是不一样的。它既可以指向常量也可以指向非常量,不能改变对象的值。但是非常量对象可以通过其他途径改变值
●指向常量的指针
1 | const double pi = 3.14; |
const指针:指针是对象,也可以限定为常量(必须初始化)
把*放在const之前,说明指针是一个常量
不变的是指针本身的值,而非指向的那个值
1 | int errNumb = 0; |
2.4.3 顶层const
顶层 const 表示指针本身是个常量,底层 const 表示指针所指的对象是一个常量。顶层 const 对任何数据类型通用,底层 const 只用于引用和指针。顶层 const 的指针表示该指针是 const 对象,因此必须初始化。底层 const 的指针则不用。
实际上只有指针类型既可以是顶层 const 也可以是底层 const,因为引用实际上只能是底层 const,常量引用即为底层 const,不存在顶层 const 的引用。
从右向左读来判断是顶层 const 还是底层 const。
对于指针和引用而言,顶层 const 在右边,底层 const 在左边。对于其他类型,全都是顶层 const
执行对象的拷贝操作时,不能将底层 const 拷贝给非常量,反之可以,非常量将会转化为常量。
1 | int i = 0; |
2.4.4 constexpr和常量表达式(const expression)
常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。
字面值属于常量表达式,由常量表达式初始化的 const 对象也是常量表达式。
1 | const int max_files = 20; //是 |
cosntexpr变量
C++11标准规定,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式。
一定是一个常量
必须用常量表达式初始化
需要在编译时就得到计算,声明constexpr时用到的类型必须显而易见,容易得到(称为:字面值类型)。自定义类型(例如:Sales_item)、IO库、string等类型不能被定义为constexpr
cosntexpr 指针的初始值必须是 nullptr 或 0 或存储于固定地址的对象。函数体之外的对象和静态变量的地址都是固定不变的。
1 | constexpr int mf = 20; |
指针和constexpr
限定符仅对指针有效,对指针所指的对象无关
1 | constexpr int *np = nullptr; //常量指针 |
注意区分 constexpr 和 const 。constexpr 都是顶层 const,仅对指针本身有效。
区分const和constexpr
constexpr 限定了变量是编译器常量,即变量的值在编译器就可以得到。
const 则并未区分是编译器常量还是运行期常量。即 const 变量可以在运行期间初始化,只是初始化后就不能再改变了。
constexpr 变量是真正的“常量”,而 const 现在一般只用来表示 “只读”。
2.5 处理类型
随着程序越来越复杂,程序中的变量也越来越复杂。
拼写变得越来越困难。
搞不清楚变量到底需要什么类型。
2.5.1 类型别名
作用:提高程序可读性
1 | typedef double wages; |
对于指针这样的复合类型,类型别名的使用可能会产生意想不到的结果:
1 | typedef char *pstring; |
有两种方法定义类型别名。
typedef double wages; // 使用 typedef 关键字
using wages = double; // 使用 using 关键字进行别名声明
typedef 作为声明语句中的基本数据类型的一部分出现。含有 typedef 的声明语句定义的不再是变量而是类型别名。和其他声明语句一样,typedef 的声明语句中也可以包含类型修饰符,从而构造符合类型。
typedef wages base, p; // base 是 double 的别名,p 是 double 的别名。
指针、常量和类型别名
typedef char* pstring; const pstring cstr = 0; // 注意:const 是一个指向 char 的常量指针。不能采用直接替换的方式将其理解为 const char* cstr = 0,这是错误的。
2.5.2 auto类型说明符
auto 说明符让编译器根据初始值来分析表达式所属的类型。理解:使用 auto 会增加编译时间,但不会增加运行时间。
1 | auto item = val1 + val2; |
auto 可以在一条语句中声明多个变量,但是多个变量必须是同一个基本数据类型(整型与整型指针和整型引用算一个类型)。
复合类型、常量和auto
编译器推断出的 auto 类型有时和初始值并不一样,编译器会进行适当的调整:
- auto 根据引用来推断类型时会以引用对象的类型作为 auto 的类型。
- auto 一般会忽略掉顶层 const,因此对于非指针类型的常量对象,auto 推断出的结果是不含 const 的。如果希望 auto 是一个顶层 const,需要明确指出。
- auto 会保留底层 const。
概括一下就是 auto 会忽略引用与顶层 const。
1 | int i = 0, &r = i; |
int 与 int *、int & 是一个基本数据类型,而 const int 与 int 不是一种类型。
用 auto 定义引用时,必须用 & 指明要定义的是引用。
2.5.3 decltype类型指示符
当希望获得表达式的类型但是不要值的时候,可以使用类型说明符 decltype。
如果 decltype 使用的表达式是一个变量,则它返回该变量的类型(包括顶层 const 和引用在内)。
decltype 与 auto 的不同:decltype 不会忽略引用和顶层 const。
注意当获得的类型是引用时,必须初始化。
1 | decltype(f()) sum = x;// sum的类型就是函数f返回的类型 |
引用从来都是作为对象的别名出现,只有在 decltype 处是例外。
decltype 和引用
如果 decltype 使用的表达式不是一个变量,则 decltype 返回表达式结果对应的类型。可以使用这种方式来保证不获取引用类型。
注意:如果表达式的内容是解引用操作,则decltype将得到引用类型。给变量加括号的结果也是引用类型。赋值操作的结果也是引用类型。
1 | int i = 42, *p = &i, &r = i; |
decltype((var)) 的结果永远是引用,而 decltype(var) 的结果只有当 var 本身就是引用时才是引用。
2.6 自定义数据结构
一组数据以及相关操作的集合
2.6.1 定义sales_data类型
类定义:类定义可以使用关键字class或struct
二者默认的继承访问权限不同
struct是public的,class是private的
1 | struct Sales_data{ |
数据成员定义了类的对象的具体内容,每个对象有自己的一份拷贝。
struct+类名+类体+分号。类体可以为空。
struct Sales_data{}; // 注意:结尾加分号
定义类时可以给数据成员提供类内初始值以进行初始化。没有类内初始值的成员则被默认初始化。
类内初始值可以放在花括号中或等号的右边,不能使用圆括号。
类使用:
1 |
|
输出结果
1 | 1-1 5 5 |
2.6.3 编写自己的头文件
类通常定义在头文件中,类所在头文件的名字应与类的名字一样。
头文件通常定义那些只能被定义一次的实体,比如类、const、constexpr 等。
头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明。
预处理器概述
确保头文件多次包含仍能安全工作的常用技术是预处理器。
预处理变量有两种状态:已定义和未定义。一般把预处理变量的名字全部大写。
整个程序中的预处理变量包括头文件保护符必须唯一,通常基于头文件中类的名字来构建保护符的名字,以确保其唯一性。
c++ 中包含三个头文件保护符:
- #define:把一个名字设定为预处理变量
- #ifndef:当且仅当变量已定义时为真,一旦检查结果为真,则执行后续操作直到遇到 #endif 为止
- #endif
预处理变量无视作用域的规则,作用范围是文件内
0YDK.png)







