C++ Primer基础之拾记——第二章(到const限定符之前)
前两天看了C++Primer的第二章部分内容,有些点容易混淆和忘记,这里做个简单的笔记,算是一个小的复习
目录
C++ Primer基础之拾记——第二章(到const限定符之前)
整形(包括char、int,long, short, long long、bool)
浮点型(float(占据1字空间,一般1字=32bit)、double(2字)、long double(3到4字))
基础数据类型:算数类型和空类型(void)
算数类型:
-
整形(包括char、int,long, short, long long、bool)
- 字符类型(char、wchar_t, char13_t, char16_t):
字符类型除了基本的char(最小尺寸8bit)类型外,还有扩展字符,如wchar_t(16位)、char16_t(16位)、char32_t(32位),主要是为了表示更多的字符集;
字符类型中的扩展字符没有符号区分
- bool型:
1 .其他类型转换到bool型会有类型转换,非零或费空变量转为true
2. bool型同样没有符号区分;
- 整形剩下的char、int、long、long long:
char、int、short、long以及long long有符号的区分——其中字符类型有三种(char,signed char和unsigned char,具体表现只有两种,由不同编译编译器决定),int等其他的有两种:默认的int、long、long long为有符号;unsigned int、unsigned long、unsigned long long为无符号;
其中需要关注一下无符号类型的对象与有符号类型对象计算过程中可能存在数据类型转换的过程——因为要始终要确保计算结果不能超过无符号类型所对应的值得表示范围,因此有符号类型有可能要先从有符号转变成无符号,具体过程是取模操作(这里取模操作的计算的数字分别为:a——有符号类型变量的数值,b——无符号类型所能表示的数值总数,如8位无符号字符可表示2^8个数,取a%b的结果;如有符号类型的-1转换成8进制的无符号类型这需要求-1/2^8的模,运算步骤为1:先求整数商c=a/b(有个口诀是:求模向负无穷取第一个整,求余数向0方向取第一个整),因为这里求得是模,所以-1/2^8=-1;第2步:模 r=a-(c*b)= -1 -(-1*2^8)=255)
浮点型(float(占据1字空间,一般1字=32bit)、double(2字)、long double(3到4字))
浮点型分为float、double、long double,他们都有符号区分(有符号和无符号);在定义浮点变量时,使用float时最好用double(可表示更精确小数,同时计算耗时与float差不多)
字面值常量
形如字面意思,如‘a’,“hello world”,10分别是字符、字符串和整型字面值;
- 1. 整形字面值:
1.整型字面值分为10进制、8进制(用0开头的数字)和16进制(0x开头的数字)——形如24(10进制24),024(8进制),0x24(16进制);
2.字面值有对应的类型,具体类型由数值大小和符号决定:
默认情况下10进制字面值是带符号数,十进制字面值的类型时能保存其值得int、long、long long中尺寸最小的;10进制字面值就算带负号,负号也不再字面值中;
8进制和16进制字面值可能带符号也可能不带符号,其类型为能保存其字面值int、unsigned int、long、unsigned long、long long 和unsigned long long中最小尺寸;
3.short类型没有字面值
- 2. 字符和字符串字面值
1.‘a’——字符字面值,
2.“a”——字符串字面值(字符数组),字符串字面值是一个字符数组,实际长度比字符序列要多1,因为最后一个为空字符(‘\0’)
- 3. 转义字符
转义字符——用来表示两种无法直接使用的字符:不可打印,无可视图符的字符(如制表符,空格符,退格符等)+ 特殊含义字符(如单引号、双引号、问号、反斜线等)
1.转义字符可以使用泛化转义序列:\x(跟1到若干16进制数) or \(跟1到3个8进制数,超过3个数的数字不计入8进制的转义,而是单独的字符),用8进制数或16进制数表示对应的字符;
-
4.指定字面值的类型
![表2.2定义的前缀和后缀](https://img-blog.csdnimg.cn/20200630231126567.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl8zNzc5MjkxMg==,size_16,color_FFFFFF,t_70)
-
bool字面值和指针字面值
1. true和false是bool型的字面值
2. nullptr是指针的字面值
-
变量
变量(对象)——具名的,可供程序操作的存储空间。
每个变量都具有某个数据类型, 类型决定了变量的3种属性:
1)类变量所占的内存空间和布局方式,
2)限定了该变量的存储空间所能表示的数值范围,
3)变量所能参与的运算
C++中对对象采用普遍的用法,对象是具有某种数据类型的内存空间。
-
1.变量的定义与初始化
变量的定义为:基本数据类型(类型说明符) (一个或多个)声明符列表。声明符列表用逗号分隔,最后以分号结束。
int sum = 0, value, unsts_sold = 0; // sum、value和unsts_sold都是int类型变量
Sales_item item; // item是Sales_item类型变量
std::string book("0-2-1-78345-X"); // book是string(可变长度字符序列)类型变量
1.在创建一个变量时并赋予其一个特定的值,这个对象就被初始化了,如上面的sum和unsts_sold。
2.初始化是将值拷贝到对象的内存单元,而赋值是擦除当前变量的内存中的值,并写入新的值;
3. C++中变量初始化的其他形式和列表初始化:
C++中初始化形式:如将变量初始化0,有4种形式,其中C++11中新加的一个是列表初始化——用{}括住字面值或者被初始化的对象。
// 形式1:
int units_sold=0;
// 形式2:
int units_sold(0);
// 形式3:
int units_sold = (0);
// 形式4:
int units_sold = {0}; // 列表初始化
列表初始化有一些特殊的性质:当内部类型变量进行列表初始化,如果发生信息丢失,编译器在编译过程中会发生错误,从而避免一些错误,如下面:
long double id = 3.1415926;
int a{id}, b = {id}; // 编译错误, id不能从long double转换到int,因为有信息丢失的风险
int c(id), d = id; // 编译将正确进行
-
2. 变量的默认初始化
变量如果没有初始化,将进行默认初始化,根据变量具体类型和变量定义的位置分三种情况:
-
内置类型变量:
定义在函数体之外 | 定义在函数体制内 |
---|---|
默认初始化为0 | 不被初始化,值为未定义,使用它 可能发生意想不到的错误,所以最好给 函数体内的内置类型变量给定初始值 |
2.定义的类一般都会指定没有被初始化时取值
3. 一些类类型要求每个对象都进行显示初始化,否则报错
-
3.变量的声明与定义
声明:让程序使用别的文件中定义的变量,使用extern修饰类型名,不可显示初始化。
// 声明:
extern double pi;
// 声明与定义:
int i;
定义:extern修饰的变量如果初始化,变成定义。注意,在函数体内的声明不能进行显示初始化
Note:定义只能在一个文件中定义一次,申明可以在多个文件中进行。
-
4.变量的作用域
变量的生命周期:变量声明至变量所在作用域结束的地方,C++语言中大多数作用域使用花括号限定。
1. 全局作用域,变量定义在函数体之外,如main;全局作用域中定义的变量在整个程序范围内都可使用。
2. 块作用域——函数体内,函数体内定义的变量生命周期为变量声明至函数体结束,在函数体制外无法使用函数体内声明的变量。
3. for语句中的变量,只可在for语句中使用,出了for语句该变量生命周期结束
4. "::"作用域标识符左侧为空时,::访问全局作用域(因为全局作用域不像std一样具有名字)
5. 嵌套作用域——被包含的作用域是内层作用域,包含的作用域是外层作用域
6. 最好不要定义一个和全局作用域中变量同名的局部变量
-
复合类型
由 基本数据类型 和 更复杂声明符 获得 更复杂的数据类型。现在介绍两种复合类型—— 引用 和 指针
-
1. 引用
1. 引用是对象的别名,引用是左值引用??:
int obj1 = 45;
int &k = obj1; // k是obj1变量的引用
一些注意点:
①引用不是对象
②引用一旦绑定一个对象,就不能在绑定到其他对象上(引用不可解绑,所以引用必须初始化)
③将引用作为初始值,是将引用绑定的对象的值作为初始值;给引用赋值,实际是给引用绑定的对象赋值
④引用的类型要与绑定的对象的类型严格匹配!!!!!
2. 引用定义时必须绑定到对象上,除了const 修饰的某基本数据类型的引用,如const double &m,(m的类型时const double &)外,引用初始化不能是字面值常量和表达式结果;
int value = 1024;
int &r = value; // 合法引用
int &l; // 定义不合法,定义引用时必须绑定到对象上
int r = 555; // 给引用赋值,实际是给引用绑定的对象赋值
double &s = 3.9801 // 引用不能在定义时接受非对象的值或者表达式
2. 指针
指针是“指向(point to)”另外一种类型的复合类型。
1.指针的一些特点:
1.指针是对象
2.指针可以在定义时可以不进行初始化,函数体内部未初始化的指针和内部变量一样具有不确定的值(不推荐定义一个值不确定的指针,当不知道指针要指向那个对象时,使用指针的字面值nullptr定义一个空指针)
2.指针的定义:
将声明符写成 *d(d是变量名,或者说声明符)的方式,指向某个数据类型;
// 定义指针:
int *ip1, *ip2; // ip1和ip2的类型都是int*,ip1和ip2应该指向int类型的变量!!!
double dp, *dp2; // dp和dp2的类型都是double*,即指向double类型的指针
指针指向对象,通过&获取对象的地址
// 指针指向对象
int ival = 42;
// &表达式的右侧,是取地址运算符,即将ival的地址拷贝给指针p
int *p = &ival;
// 先定义p为int*, 即一个指向int的指针变量,然后将p初始化使其指向int类型变量ival
Note:
2.1.指针类型与指针所指对象类型的匹配问题
指针的类型:
形如:
int *p
//或者
double *s
其中p是一个整形指针,换种表达形式即p的类型时int*;而s是一个浮点型指针。
在声明语句中,定义的指针的类型实际上指定了指针所指对象的类型,所以指针的类型应该与指针所指对象的类型严格匹配。
3.指针值(即地址),即指针应该接受的赋值情况:
①.指针指向一个对象,获取对象的地址。如:
int i = 5; int *p = &i; // 合法
p = 42; // 错误,p虽然存储的是对象的地址,但不能是一个字面值
int m = 6;
p = &m; // 这是合法的,现在p指向了int类型变量m
②.指向紧邻对象所占空间的下一个位置
③. 空指针
int *p = 0;
// 或者
int *p = nullptr;
④.无效指针(除1到3所描述的指针)
试图访问②~④中描述的定义好的指针容易发生错误,最保险的就是使用指针指向某一个对象,因为本身指针就是帮助我们间接访问某对象的。
4. 利用指针访问对象
*在表达式中是解引用符,通过解引用符可以获得指针所指的对象。
int val1 = 42;
int *p = &ival1;
cout << *P; // *p出现在表达式中,获得val1,所以输出42
*p = 0; // 通过指针改变val1的值
&与*在语句中的位置不同,所具有的含义不同:
& | * | |
在变量的定义语句中, 紧随类型说明符出现 | 与声明符组成引用 | 与声明符组成指针 |
在表达式中 | 获取对象的地址 | 获取指针所指的对象 |
具体说明:
5.空指针:
使用int *p=0;或者int *p1 = nullptr,或者int *p2=NULL定义一个空指针;空指针的值为0,即p=0,p1,p2都等于0.
Note:指针保存的是一个地址,但函数体中未被初始化的指针在大多数编译器环境下,其指针所占空间的内容(即地址)是一个随机的值。访问这个指针就表示去访问一个随机地址的对象,有可能造成不合法的问题。所以要对每一个指针进行初始化,不初始化也要初始化城空指针。
6.指针其他操作:
一个合法的指针,可用在条件表达式 和 比较运算中。
条件表达式:
合法指针 | 指针值 | 条件语句中的结果 |
空指针——int *p=0; 此时p=0 | if(p)--> 条件表达式返回false | |
指向某个对象: int val=100, *p = &val;
| if (p)--> 条件表达式返回true |
比较运算: 相同类型的指针可用相等(==)或者不等(!=)运算符进行比较:
指针值相等的三种情况,
①都是空指针,
②都指向一个对象,
③都指向同一对象的下一个地址
7. void*指针
void *pv,void*指针可以存放任意类型的变量,甚至指针。
double pi = 3.1415, *pd = π
void *m = π
m = pd; // 这里是合法的
但是void*指针能做的事比较少
void*能做的事:①与其他指针比较;②作为函数输入输出;赋值给另外一个void*指针。
不能通过void*指针直接操作其所指对象,因为我们不知道void*指针指向的对象的类型!