第二章 变量与基本类型(上)

目录

2.1 基本内置类型

一、算术类型(arithmetic type)

 二、类型转换

1.系统自动的类型转换

2.有符号值和无符号值的混用

三、字面值常量

1.整型和浮点型字面值

2.字符与字符串字面量

 3.转义序列

4.补充:泛化转义序列

2.2变量

一、变量的定义

1.基本形式:

2.初始值

3.声明与定义,赋值与初始化的区别(想要再透彻一点请自己再往深处查询,此处不做过多赘述)

  4.变量初始化与定义需要注意的点在于:

二、标识符(identifier)

三、变量的作用域

2.3 复合类型

一、引用 

1.引用的概念作用及形式

2.引用需要注意的地方

二、指针(此处仅为了解,详细讲解请查收后面的文章)

1.指针概念与形式

2.指针VS引用

3.获取对象地址

 4.指针要注意的点(欢迎补充)


2.1 基本内置类型

C++的基本数据类型分为两类,算术类型(arithmetic type)空类型(void)。注意:空类型不对应任何具体的值,仅用于一些特殊场合,例如,当函数不返回任何值时使用空类型作为返回类型。

一、算术类型(arithmetic type)

1.算术类型分为整型浮点型。其中,整型内包含整数型,字符型和布尔类型,而浮点型就是浮点数类型。

2.各算术类型的尺寸

1B = 8b (1字节 = 8比特)

1word = 2B (1字 = 2字节)

              类型                  含义         最小尺寸
              bool             布尔类型          未定义
              char                   字符             8bit
            wchar_t                 宽字符            16bit
            char16_t            Unicode字符            16bit
           char32_t            Unicode字符            32bit
              short                短整型            16bit
               int                 整形            16bit
              long               长整型            32bit
          long long                长整型            32bit
              float          单精度浮点数 6位有效数字/4B/32b
            double            双精度浮点数10位有效数字/8B/64b
         long double        扩展精度浮点数10位有效数字/8B/64b

特殊的,long long类型是在C++11中定义的;扩展精度浮点数long double有80bit,相当于一个double类型的64个bit加上16个bit,其中16个bit有12个加在尾数而剩余的4个bit则会在阶码位出现(仅作了解用,详细资料请自行查验)。

3.带符号类型(signed)与无符号类型(unsigned)

Ⅰ.二者区别很显然在于符号的有无,前者带符号类型可以表示整数,负数或0;而无符号类型仅能表示大于等于0的值。

Ⅱ.普通的,int,short,long,和long long都是带符号的,正数负数皆可表示;而在它们前面加上unsigned之后就是不带符号的,只能表示整数和0。注意:unsigned int可以缩写为unsigned。

4.类型总结

Ⅰ.对于数据类型的选择应该贴近简化而做出限定;

Ⅱ.当已知数据不可能为负数时用无符号unsigned型;

Ⅲ.整数运算一般使用int,当超出int范围时我们用long long;

Ⅳ.浮点数运算时一般用double双精度而不用float单精度,因为double比float精度高,二者       计算代价几近相同;而long double则是不必要的,一则精度太高,二则运算代价太大。

 二、类型转换

我们在很多地方都需要数据类型转换,即将数据从一种已给定的类型转换为另一种相关的类型。

1.系统自动的类型转换

发生:在使用了一个类型但其实应该取另一种类型时,系统会自动进行数据类型转换。

如下代码:

#include<iostream>
int main()
{
	bool b = -42;//b的值被bool类型约束成1或0,而值非0皆为真,所以b = 1
	int i = b ;//将b的值1赋给i,同样i的值被int约束为一个整数所以i的值为1
	i = 3.14;//重新给i赋值,因为i的类型始终是int,所以只取整数位,i的值为3
	double pi = i;//将i的值赋给pi,pi被double类型约束为双精度浮点数,所以pi的值为3.0
	unsigned char c = -1;//8bit的c,值为255
	signed char d = 256;//c2的值是未定义的
	//std::cout << b << std::endl << i << std::endl << c << std::endl << d;
    //上一行可以输出这些值供运行查看
	return 0;
}

特别的,需要注意的是上面代码段的第八行c值和第九行d值。c的值我们赋的是-1,但这是一个无符号类型的数,结果是初始值对数值总数取模后的余数,那么c就是初始值-1对unsigned char数值总数256的取模结果,即为255。而对于带符号的d值来说,将超出范围的值赋给带符号值,结果是未定义的。此时,系统可能会崩溃,可能会继续工作,也可能会产生垃圾数据。

2.有符号值和无符号值的混用

内容:有符号值和无符号值在混用时系统会将有符号值转换成无符号值。如下代码:

#include<iostream>
int main(){
	int i = -42;//有符号的int
	unsigned u = 10;//无符号的int
	std::cout << i + i << std::endl;//两个有符号的运算
	std::cout << u + i << std::endl;//一有一无的运算
	return 0;

 输出为:

-84
4294967264

我们可以看出两个有符号的结果如预期,但有符号的和无符号的混用的结果则相当令人不知所措。

这是因为系统在进行混用运算时,先将有符号的数转换成无符号的。换言之,给无符号值赋一个负值,结果为该负值加上无符号数的模。在上述代码中就是将-42赋值给一个unsigned类型的数,结果为:

i = -42 + 4294967296 = 4,294,967,254

i + u = 4,294,967,254 + 10 = 4,294,967,264

3.无符号数在条件判断中的应用

我们知道无符号的数永远不会小于0,这就导致在循环条件判断中容易出现死循环(条件永远满足一直执行下去,永不停歇)。

例如:一个简单的for循环输出0-10共11个数字。

#include<iostream>
int main(){
	for(int i = 10 ; i >= 0 ; --i){
		std::cout << i << std::endl;
	}
	return 0;
}

在学习了unsigned类型的数值之后呢,觉得不会输出负值,不如定义成unsigned类型,节省资源,但是这样会导致这个for循环称为一个死循环。

就像这样:

#include<iostream>
int main(){
	for(unsigned i = 10 ; i >= 0 ; --i){
		std::cout << i << std::endl;
	}
	return 0;
}

大家可以自己写也可以copy下来去运行一下,就会发现,这真的是一个死循环。

析:i从10递减到0时时没有异议的,问题在于,当i = 0时,程序执行下动作。当i的值是0时,也满足for循环的条件,这个时候执行--i值为-1,但我们约束了i是一个无符号的数(始终大于等于0),而-1不满足这个条件,所以系统会将i的值强制转换成一个合法的无符号数值,也就是:

-1 + 4294967296 = 4294967295

这个时候--i的值也就是4294967295。等递减到0之后周而复始还是4294967295,形成无闭环的死循环。

解决的方法如果想不到,那就尝试换一种循环,将for循环换成while循环试试看。

#include<iostream>
int main()
{
	unsigned a = 11;
	while(a){
		--a;
		std::cout << a << std::endl;
	}
	return 0;
}

在上面的while循环体中,我们不同于for循环体做出的改动是让变量先-1,再去执行输出的动作,

而改变的成果在于,在a的值为1时,a--就变成了0,再次进入下一次循环之前因为while条件为非0,这个时候a=0,所以不会执行,从而跳出循环。而需要注意的是,因为在while循环体内先执行了一次自减,所以值会小1,因此我们需要将a的初始值加1从而实现。

三、字面值常量

一看就知道值的值叫做字面值常量(literal),就像一个数,3,5等等这样一看就知道值的值。每个字面值对应一种数据类型,而字面值常量的形式和值决定了它的数类型。

1.整型和浮点型字面值

整型字面值分为:八进制(0开头),十进制,十六进制(0x或0X开头)。

例如:32(十进制),                             040(八进制),                                     0x20(十六进制)

 浮点型字面值则默认为一个double,可以用后缀l或L表示。

2.字符与字符串字面量

字符常量:由单引号括起来的一个字符//'a'

字符串常量:由双引号括起来的零个或多个字符//"Hello World!"

字符串常量实际上是由常量字符构成的数组(Array),后续会讲到,不必着急。

当输出过长字符串常量时,可以分开述写:

std::cout << "This is a two long string literal "
             "that I can't write it by only a line" << std::endl;
 3.转义序列

转义序列分为两类:不可打印的字符(没有可视的图符,如回车,退格等)和有特殊含义的字符输出

(如可视符号,\,%,!等等)。输出有特殊含义的字符时需要用到转义序列,转义序列均以反斜线(\)开始。

                          名称                          符号
                        换行符                           \n
                    纵向制表符

                           \v

                    横向制表符                           \t
                       反斜线                           \\
                       回车符                           \r
                       退格符                           \b
                         问号                           \?
                       进纸符                           \f
                  报警(响铃)符                           \a
                   单(双)引号                          \'(\")

转义字符被当作一个字符使用,如下代码段:

std::cout << '\n' ;
4.补充:泛化转义序列

 同样,我们可以使用泛化的转义序列,形式是:\x后加1或多个十六进制数字,或\后加1个或2个,3个八进制数字,数字部分表示的是字符对应的数值。

#include<iostream>
int main()
{
	std::cout << "Hi \x4dO\115!\n";
	std::cout << '\115' << '\n' ;
	return 0;
}

输出为:

Hi MOM!
M

注意:二者不同的是\后面加最多三位八进制数字,换言之,如果\后面跟的数字超过三位则转义序列不起作用;而\x后面加的十六进制数字则是以对所有数字都有效

2.2变量

一、变量的定义
1.基本形式:

类型说明符+变量名组成的列表

int a = 520, b , //a,b,c都是int类型的值
    c = 1314 ; //多个变量中间用逗号隔开,末尾用分号结束
std::string book("0-201-78345-X) /*book通过一个string字面值初始化,string是一种库类型,
                                  */表示一个可变长的字符序列
2.初始值

对象被创建时获得了一个初始值,我们说这个对象被初始化了。同时,在同一条定义语句中,可以用先定义的变量去初始化定义的其他变量。(注意:是链式定义变量而不是链式初始化变量)

3.声明与定义,赋值与初始化的区别(想要再透彻一点请自己再往深处查询,此处不做过多赘述)

引用型声明关键字为extern,用于声明外部变量,不为它分配内存;而定义型声明则是声明一个新变量并为它分配一定的内存声明可以多次,但定义只能一次(尤其是多个文件中)。

初始化与赋值的相同点在于赋予变量新的值;区别则在于初始化赋值时在给变量分配内存时,而赋值则是定义以后用到此变量时顺手赋值。在值方面来说,初始化是赋予一个初始值,而赋值则是擦除当前值,用一个新的值取而代之。

总而言之,声明一个变量时,须要同时进行初始化操作;而定义变量则仅需要赋值操作。

  4.变量初始化与定义需要注意的点在于:

对于函数的内置类型来说,如果为进行初始化,则该值为未定义;

对于类的对象来说,如果未进行显式初始化,值由类定义的默认值代替。

所以,建议对每一个内置类型的变量进行初始化操作。

二、标识符(identifier)

1.组成:字母、数字、下划线,必须以字母或下划线开头;长度不限,但大小写敏感(即grain和Grain是不同的)

2.不能作为标识符的是:C++中的保留字(注意关键字与保留字的区别:关键字是具有特殊含义的保留字),C++中共有73个关键字都不能作为标识符(如:char,int,while,for,if,true,this等),请自行查询,此处不做过多赘述。

三、变量的作用域

大多数以花括号分割;同一个名字在不同作用域内能指向不同实体,名字的有效区域始于名字的声明语句,以声明语句所在作用域末端为结束。

全局作用域:典型的像main语句等定义于所有花括号(函数体)以外的;一旦定义整个程序范围内皆可使用;

局部作用域:与全局者对应的就是局部者,也就是块作用域;仅仅能在这个函数体内使用,在函数体外无法使用。

2.3 复合类型

C++中的复合类型有很多,例如:结构体,共用体,枚举,指针,引用等。本处我们先着重讲一下引用。

一、引用 

首先先了解一点,一般术语的引用指的是左值引用,至于右值引用,如果后续有机会的话会讲。

1.引用的概念作用及形式

引用(reference)其实就是给对象另起一个名字(此处等价于将引用量与对象绑定),那么有同学就要问了,既然只是单单的取个昵称,那引用存在的意义是什么呢?好问题。

首先,引用的内核本质上来说还是与它绑定的实参对象,系统动引用也就是改变了它背后绑定的对象,所以不会出现数值不一的情况

其次,引用是一个形参,操作起来不需要单独开辟空间浪费资源,还可以提高函数运行效率;其实引用的作用和指针很相似,二者在这里的区别就是在调用函数时,系统需要为指针开辟一处空间,而引用则不需要。

 引用的形式:&变量名(如:下面代码段第二行)

int ival = 1024;
int &refval = ival;

上述中,定义了一个实参val,随后定义了一个引用也就是&refval,第二行的意思是将&refval与定义的ival整型对象绑定,注意:绑定时需要类型相同。

2.引用需要注意的地方

Ⅰ.定义引用是将引用与初始值绑定起来,而不是用时再赋值,引用绑定的对象不可更改且一直绑定(就像最初的DR[狗头保命]),因此定义一个引用时必须初始化(引用类型必须和初始化的对象类型相同);

Ⅱ.引用本身不是对象,但引用只能绑定对象,所以注定了引用不可以再定义引用,(如果学过数据库的话,也可以理解为定义在基本表上的视图,视图是基本表查询的结果映射,跟随基本表的数据变化而变化,但不可以基于视图再定义另一个视图);

Ⅲ.由第一条引用只能且必须绑定对象来说,我们可以知道对于引用的操作,其实是对于与它绑定的对象的操作。换言之,取一个引用的值就是取与它绑定的对象的值,给一个引用赋值,就是给与它绑定的对象赋值

请看以下典例:

int a = 1;//正确的:定义了一个整型对象
int &a1 = a;//没毛病的:定义了一个整型引用并与整型对象a绑定
int &a2;//错误的:因为定义变量必须初始化!!
int &a3 = 1024;//婉拒了哈!引用的初始值必须是对象
int &a5 = a1;//随手定义一个引用,但是这行也是不对的,因为引用本身不是对象!
double &a6 = a;//NoNoNo,也是不可以的,因为引用类型必须与绑定对象类型相同!!

也可以看一下面这个代码,先看看应该是什么再去验证:

#include<iostream>
int main()
{
	int i ;
    int &ri = i;
    i = 5; ri = 10;
	std::cout << i << " " << ri << std::endl;
    return 0;	
}
二、指针(此处仅为了解,详细讲解请查收后面的文章)

现如今,人们对于指针的理解较为繁杂,因此在这里我们只讨论已经被大多数人认同的,暂时不谈有争议的或本人不敢苟同的。

1.指针概念与形式

指针与引用作用类似,实现对其他对象的间接访问。指针内部存的是某个对象的地址。定义形式是:*变量名,如:*d。

2.指针VS引用

首先,指针本身就是一个对象但引用不是,所以可以对指针进行赋值和拷贝;

然后第二,指针在定义时无须初始化,而引用则一定要在定义时初始化;

第三,指针本身有内存而引用则没有。

3.获取对象地址

我们说指针存的东西到底是什么,其实就是某个对象的地址,那么我们取指针内容,也就是取地址,因此我们需要用到取地址符(&)(如下代码)。

int a = 1024;//定义一个整型变量a
int *p = &a;//p存放a的地址,换言之p是整型变量a的指针
 4.指针要注意的点(欢迎补充)

指针类型要与对象类型相同

double pd;
int *p = pd;//错误!因为int类型不能匹配double类型
int *q = p;//正确!初始值为指向int型的指针

好累啊,就先这样吧,还有一些下次丕定~~~~

  • 27
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C.G.道枝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值