《C++Primer》读书笔记(二)C++基础(上)

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=&pi;  //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;
···

发布了34 篇原创文章 · 获赞 5 · 访问量 3万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章