Skip to content

Latest commit

 

History

History

Chapter02

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 

第二章 变量和基本类型 学习笔记

👉 【练习题

数据类型是程序的基础:告知数据的意义以及能在数据上执行的操作。

C++支持广泛的数据类型,定义了基本内置类型和为用户提供了自定义数据类型的机制。

数据类型决定了程序中数据和操作的意义

1. 基本内置类型

1.1 算术类型

类型 最小尺寸 字面值
bool 布尔类型 未定义 真(true)或假(false)
char 字符 8位
w_char_t 宽字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位
int 整型 16位
long 长整型 32位
long long 长整型 64位
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10位有效数字

计算机中以比特序列存储数据,每个比特非0即1。

浮点型可表示单精度、双精度和扩展精度值

1.2 有符号类型和无符号类型

除布尔型和扩展的字符型外,其他整型可划分为 有符号整型(signed)无符号整型(unsigned) 两种。

  • 有符号类型:可表示正数值、负数值或0
  • 无符号类型:表示大于等于0的值。

注意:C++标准未规定有符号类型如何表示,但约定了在表示范围内正值和负值的量应该平衡。

1.3 类型转换

定义:将对象从一种给定的类型转换为另一种数据类型。

注意:切勿混用有符号类型和无符号类型。各个类型都有一个自身可表示的范围,超出时会导致程序崩溃(一般是内存溢出)。

转义序列(escape sequence):以反斜线作为开始,常见例子:换行符(\n)、横向制表符(\t)、报警(响铃)符(\a)、纵向制表符(\v)、退格符(\b)、双引号(\")、反斜线(\\)、问号(\?)、单引号(\')、回车符(\r)。

2. 变量

每个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式以及存储的值的范围。

变量:具名、提供程序操作的存储空间。

2.1 变量的定义

2.1.1 基本形式

类型说明符(type specifier),其后紧跟由一个或多个变量名组成的列表。其中变量名以逗号分隔,最后以分号结束。

~~~~延伸~~~~ 对象:是指一块能存储数据并具有某种类型的内存空间。

2.1.2 初始值

在C++中,初始化和赋值属于两种不同的操作和概念。一般情况,二者的区别几乎可以忽略不计。

  • 初始化:创建变量时赋予其一个初始值。
  • 赋值:把对象的当前值擦除,而用一个新值来替代。

2.1.2 列表初始化

由一组花括号括起来的初始值,称为列表初始化(list initialization)。

2.1.3 默认初始化

如果定义变量时没有指定初始值,则变量被默认初始化(default initialized)。具体的默认值是由 变量类型 决定,同时定义变量的位置也会受到影响。

👉Notes定义于函数体内的内置类型的对象如果没有初始化,则其值未定义。类的对象如果没有显式地初始化,则其值由类确定。

2.2 变量声明和定义的关系

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

为了支持该机制,C++明确区分声明和定义

声明:规定了名字和类型。

定义:申请存储空间,也可能为变量赋一个初始值。

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

extern int i; // 声明i而非定义i
int j; // 声明并定义j

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

extern double pi = 3.14159; //定义

任何包含了显式初始化的声明即成为定义

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

C++是静态类型语言,含义是在编译阶段检查类型。检查类型的过程称为类型检查(Type checking)

2.4 标识符

C++的 标识符(identifier)字母、数字和下划线 组成,其中 必须以字母或下划线开头

👉Notes:长度无限制,对大小写字母敏感。

注意

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

变量命名规范

规范的作用:有效提高程序的可读性。

  • 标识符能体现实际含义。
  • 变量名一般用小写字母
  • 用户自定义的类名一般以大写字母开头
  • 若标识符是多个单词组成,则单词间应有明显区分。如student_load或studentLoan,简称 驼峰命名法

注意关键字(保留字)不能用作标识符

2.5 名字作用域

作用域:C++种大多数作用域都用花括号分隔。

作用域中一旦声明了某个名字,所嵌套的所有作用域都能访问该名字。同时,允许在内层作用域中重新定义外层作用域中有的名字。

👉Notes:如果函数中用到某个全局变量,则不宜再定义一个同名的局部变量。

3. 复合类型

复合类型(compound type)是指基于其他类型定义的类型。

3.1 引用

引用(reference):给某个对象起个别名。

定义引用时,程序把引用和它的初始值绑定,而不是将初始值拷贝给引用。

👉Notes引用必须初始化。引用非对象,只是为一个已知存在的对象所起的另外一个名字。每个引用标识符都必须以 符号&开头

引用与要绑定的对象要严格匹配。

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

3.2 指针

指针与引用的区别

  • 指针本身是一个对象,允许对指针赋值和拷贝,而且在指针的生命周期内它可先后指向几个不同的对象。
  • 指针无须在定义时赋初值。

注意:在块作用域内定义的指针未被初始化,则也是会有一个不确定的值。

✅阅读指针或引用的声明语句时,推荐使用 从右向左阅读 方式。

3.2.1 指针定义的语法格式

int *pt01, *pt02; //pt01和pt02都是指向int型对象的指针
double dpt01,*dpt02; // dpt01指向double型对象,dpt02指向double型对象的指针

定义指针变量时,每个变量前面必须 有符号 *

3.2.2 获取对象的地址

获取指针对象的地址,使用 取地址符(操作符&)

int ival = 42;
int *p = &ival; //p存放变量ival的地址,或者说是指向变量ival的指针。

👉Notes:引用不是对象,没实际地址,所以不能定义指向引用的指针。

在声明语句中,指针类型与被指向对象的类型要匹配一致,否则会发生错误。

3.2.3 指针值

指针的值(即地址)所属的4种状态:

指向一个对象。

指向紧邻对象所占空间的下一个位置。

空指针,意味着指针没有指向任何对象。

无效指针(上述情况之外的值)。

3.2.4 利用指针访问对象

通过 解引用符(操作符*) 来访问对象。

int ival = 42;
int *p = &ival; // p来存放ival的地址
cout << *p; // *得到指针p所指的对象

解引用操作仅适用于指向某个对象的有效指针。

3.2.5 空指针

不指向任何对象,一般是使用nullptr(特殊类型的字面值)来初始化指针,或者是使用 int *ptr= 0 中的字面值0来生成空指针。

👉Notes:新标准中一般使用 nullptr,尽量避免使用NULL

注意:使用未经初始化的指针会引发运行时错误

3.2.6 赋值和指针

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

int i = 42;
int *pi = 0; // 初始化pi,但未指向任何对象
int pi2 = &i; // pi2被初始化,存放i的地址
int *pi3; // 如果pi3定义在块内,则pi3的值无法确定

pi3 = pi2; // pi3和pi2指向同一个对象i
pi2 = 0; //pi2不指向任何对象,初始化为nullptr。

赋值语句不容易搞清是改变了指针的值还是改变了指针所指向对象的值。

✅最好办法:记住赋值永远改变的是等号左侧的对象。

3.2.7 void* 指针

void* 指针属于特殊的指针类型,可用于存放任意对象的地址。

~~不能直接操作 void* 指针所指的对象。~~对象类型未知,所以无法确定能在对象上做哪些操作。

4. const限定符

目的:定义一个值不能被改变。

4.1 初始化和const

  • const对象必须初始化原因:const对象一旦创建后其值不能被改变。

  • 默认情况下,const对象仅在文件内有效,不能被其他外部文件访问

如果想在多个文件之间共享 const对象,必须在变量的定义之前添加 关键字extern

4.2 const引用

对常量的引用(reference to const):引用指定到const对象上。

const int ci = 1024;
const int &r1 = ci; // 引用及其对应的对象都是常量

对于引用的const对象,可以读取但不能修改。

临时量(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时创建的一个未命名的对象。

C++中对临时量的引用属于非法行为。

4.3 指针和const

指向常量的指针(pointer to const):不能用于改变其所指对象的值。

const double pi = 3.14; // pi是常量,它的值不能改变
double *ptr = &pi; // ptr是一个普通指针,所以操作非法
const double *cptr = *pi; // cptr可以指向pi的值
*cptr = 42; // const类型,不能修改*cptr的值

指针的类型必须与其所指对象的类型一致。

const指针(常量指针):必须初始化。(指针本身就是常量)一旦初始化完成,则不能再改变值。

int errNumb = 0;
int *const curErr = &errNumb; // curErr将一直指向errNumb

const double pi = 3.14159;
const double *const pip = &pi; // pip是一个指向常量对象的常量指针

const之前加 * 说明指针是一个常量,说明:不变的是指针本身的值,而非指向的值

4.4 顶层const底层const

  • 顶层const : 指针本身是常量。
  • 底层const: 指针所指向的对象是一个常量。

4.5 constexpr 和 常量表达式

常量表达式(const expression):是指 值不会改变 且编译过程中能得到计算结果的表达式。

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

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

👉Notes:C++11规定,允许将变量声明为 constexpr 类型以便于编译器来验证变量的值是否是一个常量表达式。例如:

constexpr int mf = 20;
constexpr int limit = mf + 1;

5. 处理类型

5.1 类型别名

类型别名(type alias):某种类型的同义词。

定义两种类型别名的方法:

  • 传统方法:使用关键字typedef。
typedef double wages; // wages是 double 同义词
typedef wages base,*p; // base是double的同义词,p是double* 的同义词
  • C++11新标准:使用 别名声明 来定义类型的别名。
using SI = Sales_item; // SI是Sales_item的同义词

类型别名和类型的名字等价,只要是类型的名字出现的地方,就可用类型别名。

5.2 auto类型说明符

C++11新标准 特性引入。引入目的:让编译器去分析推断表达式所属的类型

⚠️注意:auto定义的变量必须有初始值。 会忽略 顶层const,从而保留 底层const

➡️使用auto关键字一条语句中声明多个变量时,语句中所有变量的初始值基本数据类型必须一样。例:

auto i = 0, *p = &i; // i是整数,p是整型指针
auto sz = 0,pi = 3.14; // sz 和 pi 的类型不一致。

5.3 decltype类型指示符

从表达式的类型推断出要定义的变量的类型(C++11新特性引入)。

作用:选择并返回操作数的数据类型。例:

decltype (f()) sum = x; // sum的类型就是函数f的返回类型

decltype 不会忽略 顶灯const

👉Notes

  • decltype((variable)) (双层括号)的结果永远是 引用

  • decltype(variable) 结果只有当 variable 本身是引用 时才是引用。

6. 自定义数据结构

数据结构:把一组相关的数据元素组织,然后使用其策略和方法。

6.1 struct 类结构

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

类体由 花括号 形成新的作用域。

内部 定义的名字必须 唯一,但可与 类外部 定义的名字 重复

struct student stu{
  //数据成员(类的成员)
	std::string name;
	int age;
	char sex[5];
}; //结束位置必须写分号,分号表示声明符(通常为空)的结束

👉Notes:在类定义的最后不要忘记加分号。

6.2 编写头文件

头文件通常包含 只能被定义一次的实体,如 类、const 和 constexpr变量 等。

👉Notes:头文件一旦改变,相关的源文件必须重新编译以获取更新过的声明。

预处理器

在编译之前执行的一段程序,可以部分改变所写的程序。

确保头文件多次包含仍能安全工作。

头文件保护符(header guard):依赖于预处理变量的两种状态:已定义未定义

⚠️ 预处理变量无视C++语言中作用域的规则。

✅Notes:避免与程序中其它实体名字冲突,预处理变量的名字推荐使用全部大写