2.1 基本内置类型
算术类型
(1)算术类型分为:整型(包括字符、布尔)、浮点型
- 算术类型所占的最小尺寸,如下:
类型 | 最小尺寸 |
---|---|
bool | 未定义 |
char | 8 bit |
int | 16 bit |
long | 32 bit |
float | 6位有效数字 |
double | 10位有效数字 |
其中,一个char的大小和一个机器的字节一样
- char16_t 是 Unicode 字符(Unicode 是用于表示所有自然语言中字符的标准),另外,还有 char32_t
- C++语言规定,一个int至少和一个short一样大,一个long long至少和一个long一样大
*内置类型的机器实现*
- 可寻址的最小内存块称为字节(byte)
- 存储的基本单元为字(word),它通常由几个字节组成
- 大多数计算机将内存中的每个字节与一个地址关联,为了赋予内存中某个地址明确的含义,必须首先知道存储在该地址的数据的类型。类型决定了数据所占的比特数以及如何解释这些比特的内容
带符号类型和无符号类型
(1)除去布尔型和扩展的字符型之外,其他整型可以分为 signed 和 unsigned 两种
(2)char实际上会表现为有或者无符号的,由编译器决定
(3)在表示范围内正值和负值的量应该平衡,例如:8比特的signed char 理论上表示-127到127,而大多数计算机将表示范围定为-128到127
*选择类型的建议*
1. 明确知晓数值不为负,则用无符号类型
2. 一般用int、double进行运算
3. 算术表达式一般不用char或者bool,因为不同机器可能所带符号不同
类型转换
(1)强行赋值转换情况
初始值 | 赋值给 | 结果 |
---|---|---|
非 bool | bool | 初始值为0,则false;否则true |
bool | 非bool | 初始值为false,则0;否则1 |
浮点 | 整数 | 保留整数部分 |
整数 | 浮点 | 小数部分记为0(整数所占空间若超浮点容量,可能会损失) |
超出范围的值 | 无符号 | 初始值对无符号类型表示数值总数取模后的余数 |
超出范围的值 | 符号类型 | undefined |
- 避==免无法预知==和==依赖于实现环境==的行为
(2)含有无符号int又有符号int时,会将有符号int转换成无符号int
unsigned u=10;
int i=-42;
std::cout<<u+i<<std::endl; // 如果int占32位,输出4294967264
- 其中,相加前把-42转换成无符号数,把==负数转换成无符号数==类似于直接给无符号数赋负值,结果是等于这个负数加上无符号数的模
(3)死循环的例子
for(unsigned u=10;u>=0;--u)
std::scout<<u<<std::endl;
*切记混用带符号类型和无符号类型*
练习
1.阅读代码(32位)
unsigned u=10,u2=42;
int i=10,i2=42;
std::cout<<u-u2<<std::endl;
std::cout<<"/"<<std:endl
std::cout<<i-u2<<std::endl;
- 输出结果为:2^32-32 / 2^32-32
字面值常量
(1)整型:(0开头表示八进制,0x或0X表示十六进制)
- 20(十进制)
- 024(八进制)
- 0x14(十六进制)
(2)浮点型(小数,或科学计数法(指数部分用E或e标识))
- 3.14159
- 3.14159E0
- 1e0
- 浮点字面值默认为double,使用其他浮点需要加后缀
(3)字符和字符串字面值
- ‘a’ //字符
- “Hello World” //字符串
- (字符串实际为常量构成的数组(array),因此字符串的长度比内容多1,为结尾处的‘\0’)
(4)转义序列(等等)
功能 | 序列 |
---|---|
换行符号 | \n |
横向制表符 | \t |
退格符 | \b |
(5)指定字面值的类型
- 字符和字符串——前缀
- 整型和浮点——后缀
(6)布尔字面值和指针字面值
- true和false
- nullptr(指针字面值)
练习
1.利用转义序列编程,要求先输出2M,然后转到新一行。修改程序使其输入2,然后输出制表符,再输出M,最后转到新一行。
std::cout<<"2M"<<std:endl;
std::cout<<"\n"<<std::endl;
std::cout<<2<<"\t"<<'M'<<std::endl;
2.2 变量
(1)变量定义基本形式
- 类型说明符(type specifier),紧跟一个或多个变量名
- 字符串类型在标准库std中:std::String
==对象==一般指一块能存储数据并具有某种类型的内存空间
(2)初始值
初始化:创建变量时赋予其一个初始值
赋值:把对象当前值擦除,以一个新值来替代
(4)默认初始化
- 定义变量时没有指定初值,则变量被默认初始化
- 定义在函数体内部的内置类型变量将不被初始化。一个未被初始化的内置类型变量的值是未定义的,如果试图访问将引发错误
定义于函数体内的内置类型的对象如果没有被初始化,则其值未定义。
类的对象如果没有显式初始化,则值由类确定
==建议初始化每一个内置类型的变量==
变量声明和定义关系
(1)C++支持分离式编译,即允许将程序分割为若干文件,每个文件独立编译
(2)声明(declaration)使得名字为程序所知
(3)定义(definition)负责创建与名字关联的实体
==任何声明并显式初始化即成为定义==
C++是一种静态类型语言,含义是在编译阶段检查类型(成为类型检查)
标识符(字母、数字和下划线组成,数字不能做开头)
(1)约定俗成的命名规范
- 标识符要体现实际含义
- 变量名一啊不能用小写字母
- 用户自定义的类名一般以大写字母开头
- 如果标识符由多个单词组成,要有明显区分,如student_loan
(2)自定义标识符注意:
- 不能连续出现两个下划线
- 不能下划线紧连大写字母开头
- 函数体外的标识符不能以下划线开头
名字的作用域
(1)main定义于所有{}之外,有==全局作用域==
(2)定义于main{}中的名字,值能在main所在块有效,有==块作用域==
建议:当你第一次是用变量时,才定义它(便于找到变量的定义,初始化更合理)
(3)优先使用正在作用域内的名字,要区分可以对外层使用(::reused)
2.3 复合类型
引用(reference)
int ival=1024;
int &refVal=ival; //refVal是ival的另一个名字
- 定义引用时,程序把引用和它的初始值绑定在一起,而不是拷贝给引用
- 引用即别名
练习
1.下列那些定义不合法?
int ival=1.01;
int &rval=1.01; //不合法
int &rval2=ival; //不合法
int &rval3 //不合法
2.执行下面代码,会什么结果?
int i,&ri=i;
i=5;
ri=10;
std::cout<<i<<" "<<ri<<std::endl;
- 将会输出:10 10
指针
指针是“指向”另外一种类型的复合类型
与 引用 之不同
==指针本身就是一个对象==
- 允许对指针赋值和拷贝
- 在指针的生命周期内,可以先后指定几个不同对象
==指针无需在定义时赋初值==
- 与内置类型一样,指针没有被初始化,也将拥有一个不确定的值
(1)指针定义:
int *ip1,*ip2; //都是直线int型对象的指针
double dp,*dp2; //dp2是指向double型对象的指针,dp是double型对象
(3)获取对象的地址
- 指针存放某个对象的地址,要获取,要使用取地址符(操作符&)
int ival=43;
int *p=&ival;
其中,p存放ival的地址,或者说:p是指向ival的指针
> 因为引用不是对象,所以不能定义指向引用的指针
(4)指针的值(下列只有第一个能访问和拷贝)
- 指向一个对象
- 指向==近邻对象所占空间的下一个位置==
- 空指针
- 无效指针
(5)获取指针访问对象
- 如果指针指向了一个对象,则允许使用解引用符(操作符*)来访问
关键概念:多重含义的符号 * 和 &
(6)空指针
- 空指针的生成办法(可以用于检查指针是否为空)
int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL; //预处理变量,不属于命名空间std
- 一般用nullptr,因为可以nullptr可以转换成任意其他的指针类型
(7)赋值和指针
- 给指针赋值,就是令它存放一个新的地址
(8)void* 指针
- 一种特殊的指针类型,可以用来存放任意对象的地址(但是我们不值得所指对象的类型)
练习
1.下列代码的作用
int i=42;
in *p1=&i;
*p1=*p1**p1;
- p1作为指针变量,存放i的地址,随后*p1即是i,最后相当于:i=i^2
2.下列定义是否非法?
int i=0;
double* dp=&i; //不合法,double的指针不能存int的地址
int *ip=i; //不合法,int不能赋值给指针
int *p=&i; //合法
3.下列代码,为什么p合法而lp非法?
int i=42;
void *p=&i;
long *lp=&i;
- 因为void*指针可以存放任意类型指针;long指针不能存放int的地址
理解复合类型的声明
(1)变量的定义:基本数据类型+一组声明符
- 虽然基本数据类型只有一个,但是声明符却可以不同
int i=1024,*p=&i,&r=i;
- 注意:定义多个变量
int* p,r; // p是指向int的指针,r是int
(2)指向指针的指针
int ival=1024;
int *pi=&ival; // pi指向一个int型数
int **ppi=π //ppi指向一个int型指针
(3)指向指针的引用
int i=42;
int *p; //p是一个int型的指针
int *&r=p; //r是一个对指针p的引用
r = &i; //r引用了一个指针,因此给r赋值&i就是令p指向i
*r = 0; //解引用r得到i,也就是p指向的对象,将i的值改为0
练习
1.说明下列变量的类型和值
- int* ip , i , &r = i ;
// ip为int指针,i为int数,r为i的引用
- int i,*ip=0;
// i 为int数,ip为int型的空指针
- int* ip,ip2;
// ip为int指针,ip2为int数
const限定符
有时我们希望定义这样一种变量,它的值不能改变。
const int bufSize=512;
- const对象必须立即初始化
(2)默认状态下,const对象仅在文件内有效
- 解决办法:(声明和定义都添加extern)
extern const int bufSize = fcn(); //在file.cc定义
extern const int bufSize; //与file.cc中的bufSize是同一个
(3)const的引用——==常量引用是对const的引用==
- 常量的引用,不过是绑定别名给一个const,仍然不能用引用来修改其值
(4)初始化和对const引用
int i=42;
const int &i1=i; //合法
const int &r2=42; // 正确
const int &r3=r1*2; //正确
int &r4=r1*2; //非法
(5)对const的引用可能一个非const对象
int i=42;
int &r1=i; //引用r1绑定对象i
const int &r2=i; //r2也绑定对象i,但是不允许通过r2修改i的值
r1=0; //r1并非常量,i的值修改为0
r2=0; //错误:r2是一个常量引用
指针和const
(1)指向常量的指针不能用于改变其所指对象的值,要想存放常量对象的地址,只能使用指向常量的指针
const double pi=3.14; //pi是常量,值不能改变
double *ptr=&pi; //错误,ptr是一个普通指针
const double *cptr; //正确,cptr可以指向一个双精度常量
tips:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量,所以自觉地不去改变所指对象的值
(2)const指针
int errNumb=0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
==从右向左读,依然是读懂指针等复杂声明的好办法==
练习
《C++ Primer》 ,57页,题目未做,待日后补充
顶层const
指针本身是一个对象,它又可以指向另外一个对象
因此,指针本身是不是常量以及指针所指的是不是一个常量,是两个独立问题。
- 顶层const:表示指针本身是一个常量
- 底层const:表示指针所指的对象是一个常量
int i=0;
int *const p1=&i; // 不能改变p1的值,这是一个顶层const
const int ci=42; // 不能改变ci的值,这是一个顶层const
const int *p2=&ci; // 允许改变p2的值,这是一个底层const
const int *const p2=p2; //靠右的const是顶层const,靠左的是底层const
const int &r=ci; // 用于声明引用的const都是底层const
当执行对象的拷贝操作时,常量是顶层const还是底层const区别明显,其中,顶层const不受什么影响
i=ci; //正确:拷贝ci的值,ci是一个顶层const,对此操作无影响
p2=p3; //正确:p2和p3指向的对象类型相同,p3顶层const的部分不影响
练习
1.对于下面的语句,说明其顶层还是底层const?
const int v2=0;
int v1=v2;
int *p1=&v1,&r1=v1;
const int *p2=&v2,const p3=&i,&r2=v2;
2.此题未做
constexpr和常量表达式
是指值不会改变并且在编译过程就能得到计算结果的表达式
(1)constexpr变量
==一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型==
(2)字面值类型
- 算术类型、引用、指针都属于字面值类型
- 自定义类、io库、string等不属于字面值类型,即不能定义为constexpr
(3)指针和constexpr
- 在constexpr声明定义一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关
const int *p=nullptr; //p是一个指向整型常量的指针
练习
1.下面代码是否合法?
“`
int null=0,*p=null;
···