第二章 变量和基本类型
2.1 基本内置类型
c++的基本类型有:算数类型和空类型
算数类型又包括:整型(包括字符和布尔类型在类)和浮点型,如下图所示:
整型也可以划分为带符号和(unsigned)无符号两种。带符号类型可以表示正数、负数和0,无符号类型只能表示大于等于0 的数,如 long(带符号),unsigned long(无符号),注:unsinged int 可以简写为unsigned.,32位有符号int型可以表示的范围为:2^31-1 ~- 2^31=-2147483648 ~ 2147483647,无符号int型的表示范围为:0~2^32-1=0-4294967295。
与其他整型不同,char分为三种:char、signed char 和 unsigned char,其中char和signed char并不一样,signed char明确表示为有符号字符,而char则由编译器决定是有符号还是无符号类型。
类型转换相关
1、非布尔类型赋值给布尔类型,若初始值为0则结果为false,否则为true
bool b=23; //b=true
2、布尔类型赋值给非布尔类型时,初始值为false则结果为0,初始值为true时结果为1
int i=b;//由于此时b=true,因此i=1;
3、浮点数赋值给整型时,会舍去小数部分,保留整数部分(并不会进行四舍五入)
i=3.14; //此时i=3.
4、整数赋值给浮点型时,小数部分记为0
double d=i; //此时d=3.0.
5、当赋给一个无符号类型一个超出它所表示的范围的值时,结果为初始值对该类型所能表示数值总数的取模后的余数(注:一个负数对正数取模计算方法有所不同)
unsigned i=4294967297;
count<<i<<endl;//此时i为1,32位无符号int型能表示的范围为0~4294967295(总共4294967296个数),4294967297对4294967296取模得1.
6、当给一个有符号类型赋值一个一个超出去表示范围的数值时其结果是未定义的,程序可能继续工作,崩溃或者产生垃圾数据
int i=2147483648;
cout<<i<<endl; //此时i为-2147483648
7、当一个算数表达式中即包含有无符号数也包含int值时,则int型会转化为无符号数。负整数转化为无符号数,结果为这个负数加上无符号数的模
unsigned c=-10;
cout<<c<<endl; //c等于4294967286,即等于-10加上4294967296(32位无符号整型的模)
unsigned i=10;
int j=-20;
cout<<i+j<<endl;//由于算数表达式中即包含unsigned型又包含int型,因此int型的j会自动转化为unsigned型,此时j=-20+4294967296=4294967276,因此i+j=4294967286.
8、如果两个unsigned的整型相减的结果为负数,此时结果为该负数转化为unsignedx的结果,转化过程如7.
unsigned i=10,j=20;
cout<<i-j<<endl;//i-j的结果为-10,-10会转化为unsigned型,因此i-j的最终结果为4294967276;
2.2 变量
变量的定义为:类型说明符,后面紧跟一个或多个变量名组成,变量名之间用逗号隔开,最后以分号结尾。
变量的初始化不等于赋值,初始化创建变量是赋予其初始值,而赋值是把对象的当前值去除,用一个新值取代。变量初始化有多种方式,在c++11新标准中可以使用花括号进行初始化,称为列表初始化。
int i=0;//不同的初始化方法
int i={0};
int i{0};
int i(0);
注:使用列表初始化内置类型的变量时,如果初始值存在丢失数据(如浮点型转换为int型)的风险,则编译器将会报错;
double pi=3.14159;
int i{pi};//错误,使用p初始化i会导致小数点后的数据丢失,编译报错
int j(pi);//编译通过,但是小数点后的数据丢失
如果内置类型变量在定义是未被初始化,则定义在如何函数体之外的变量将会被默认初始化为0(全局变量),而定义在函数体内部的内置类型变量将不会被初始化。未初始化的变量含有一个不确定的值。至于为什么一些变量会被初始化,而另一些变量不会被初始化,涉及到c++关于存储区的知识,我会单独写一篇文章记录一下。
变量的声明和定义
使用别处定义的名字就必须包含对对该名字的声明,声明只规定的变量的类型和名字,定义除了规定变量的类型和名字以外,还申请内存空间,也可能为变量赋初始值。声明一个变量而非定义,则在变量名前面加上extern关键字
extern int i;//声明i
int j;//定义j
包含了显示初始化的声明都是定义,即使有extern关键字
extern int i=1;//定义而非声明
变量能且只能被定义一次,但是可以多次声明。
内层作用域中可以重新定义外层作用域的已有的名字,此时可能会出现覆盖情况
int a=100;
void func()
{
int a=0;
cout<<a<<endl; //输出为内层作用域的变量a(0),覆盖了外层的a
a++; //该操作不会对外层作用域的a产生影响
cout<<::a<<endl;//使用“::”可以显示访问全局变量(这里的a=100不一定是全局变量)
};
cout<<a<<endl;//输出外层作用域的a(100)
2.3 复合类型
引用和指针感觉是第二章的重点,尤其是指针,也是c++的重点难点之一。
引用
引用实际上是为另外的对象取的别名,因此引用并非为对象,比如韩梅梅的外号叫狗蛋儿,那么狗蛋儿就是韩梅梅的引用并且其他人不能再叫狗蛋儿,定义引用时,就是把引用和对象绑定在一起,因此定义引用时必须给引用初始值。比如我们取名狗蛋儿时,必须指出谁叫狗蛋儿(韩梅梅),这样才能把狗蛋儿和韩梅梅绑定在一起
int i=3; //i为Int型对象
int &j=i; //j为i的引用,引用定义时必须初始化
引用类型必须与绑定的对象类型一致(一种情况除外,后续会讲到),且引用只能绑定在对象上,不能将一个字面值绑定给引用
double d=3.14;
int &i=d;//错误,类型不一致
int &j=3://错误,不能将字面值绑定给引用
指针
指针类似于引用,也提供的对其他对象的间接访问,与引用不同之处在于指针也是一种对象,因此定义指针时可以不用初始化,且指针所指向的内容可以改变。一般情况下,指针类型和其指向的类型必须一致。指针存放的是对象的地址,如韩梅梅(对象)住在302室(地址),而传达室(指针)存放着302室这一地址。
int *i,j=10;//i是指向int型的指针但没有初始化,而j是int型的变量且初始化10
int *p=&j;//定义了指针p并指向int型的j;
符号的“*”和“&”的意义:
①在声明语句中“*”表示声明一个指针类型,“&”表示声明一个引用
②在其他语句中“*”表示取指针所指向的内容,“&”表示取地址运算符
int i=10;
int *p;//*表示声明一个指针
int &q=i;//&表示声明一个引用
cout<<*d<<endl;//*表示取d所指向的值
p=&i;//&表示取i的地址
int* m,n;//注意m为指针,而n为int型变量
空指针表示为不指向如何对象的指针,有如下两种方法定义空指针
int *p=nullptr;//c++11的新标准
int *p=0;//直接赋值为0也表示为空指针;
不能把int型变量直接赋个指针,即使变量值恰好为0也不行
int i=0;
int *p=i;//错误,不能将变量直接赋给指针
引用和指针的区别
指针和引用都提供了对对象的间接访问,引用不是对象,定义时必须绑定一个对象且绑定后便一生一世不能更改,无法令引用重新绑定到其他对象。指针是一种对象,它存放的是对象的地址,定义时可以不用初始化,另外可以更改指针存放的地址,即更改指针所指向的对象。(引用很专一,第一次认识韩梅梅就一直追随韩梅梅,指针很容易变心,上一秒稀罕韩梅梅,下一秒就可能喜欢李雷了)。
void *是一种特殊指针,可以存放任意对象的地址,由于不知道void *指针所指向对象的类型,因此不能直接操作void *指针所指向的对象。
由于指针也是对象,因此我们也可以定义指向指针的指针
int i=10;
int *pi=&i; //pi为指向i的指针,即pi存放的是i的地址
int **ppi=π //ppi为指向指针pi的指针,即ppi存放的是指针pi的地址
如下图所示:
由于指针本身也是对象,因此可以声明对指针的引用,但是不能声明指向引用的指针因为引用不是对象
int i=10,*p;//定义了变量i和指针p
int* &a=p;//a为对指针的引用
a=&i;//类似于p=&i;
对于复杂的声明可以从变量从右向左解读,最靠近变量的修饰符表示变量的类型如:
int *&a=p;//从变量a开始往左读,离变量a最近的声明符为&,表面a是一个引用,在往左为int *表示,引用a为绑定int型指针的引用。
int **p;//离p最近的声明符为*,表明p为一个指针,在往左为int *表明指针p指向的是int型指针对象,
2.4 const限定符
const 修饰的变量只能读,不能修改内容,由于const对象一旦创建其值就不能改变,因此const对象和引用一样必须初始化。只要在不修改const变量的地方,都可以使用const变量,哪怕是在表达式中,const对象只有在试图改变其值时才发挥作用。
const int i;//错误,没有初始化
const int j=10;//正确
j=20;//错误,尝试改变const常量的值会产生错误
int p=j;//正确,并没有进行改变j的操作
const的引用
将引用绑定到const对象上,称为对常量的引用,此时不同通过引用修改它所绑定对象的值。
const int i=10;//常量,不能修改i的值
const &r=i;//常量引用,不能通过引用r来修改i的值
对一个常量对象的引用必须是一个常量引用,但是对非常量对象的引用可以是常量引用也可以是非常引用
const int i=10;
int &r=i;//错误,对一个常量对象的引用也必须是一个常量引用
int j=20;
const int &r1=j;//正确,对非常量对象的引用可以是常量引用也可以是非常量引用,其含义表示不能通过r1来改变j的值,但是可以通过其他方法改变j的值
就好比领导在加班(常量)你也必须加班(常量引用),如果领导没有加班(非常量),你可以加班(常量引用)也可以不加班(非常量引用)。
另外,前面提高过,对于非常量引用不能直接绑定一个字面值,只能绑定一个对象,但是对于常量引用可以绑定一个非常量对象、字面值或者表达式。
int &r1=3;//错误,非常量引用不能直接绑定字面值
const int &r2=3;//正确,常量引用可以直接绑定一个字面值
至于为什么常量引用可以绑定一个字面值,可以参考这篇文章
指针和常量
类似于常量引用,要想存放常量的地址,只能使用指向常量的指针,指向常量的指针不能通过该指针修改所值对象的值,但是可以修改指针本身的内容,即指针保存的地址可以改变,同样指向常量的指针可以指向一个非常量的对象
const int i=1;
int j=10;
const int *p=&i;//指向常量的指针
*p=20;//错误,不能通过指针P改变其所指对象的值
p=&j;//正确,可以改变指针所保存的地址,且常量指针可以指向非常量对象
常量指针
由于指针也是对象,因此也可以声明常量指针,常量指针和其他常量对象一样必须初始化,且一旦初始化指针所保存的地址将不能改变,但是可以通过指针改变其所指向的值
int i=10,j=20;
int *const p=&i;//常量指针必须初始化
*p=30;//正确,可以通过常量指针改变所指向对象的值
p=&j;//错误,不能改变指针所保存的地址
另外还有指向常量的常量指针,此时既不能改变常量所保存的地址,也不能通过指针改变其所指向的对象
const int 1=20;
const int *const p=&i;//指向常量的常量指针
指向常量的指针:好比一个篮子(指针)里面装有5个苹果(对象),你不能改变篮子里面苹果的数目(不能通过指针改变对象的值),但是你可以用蓝子装香蕉、梨等其他水果(可以改变指针指向的对象),同样当你装好香蕉后,你也不能改变篮子里香蕉的数目。
常量指针:好比你买 了一个篮子专门用来装苹果(定义常量指针且初始化),那么这个篮子就只能用来装苹果不能装其他水果(不能改变常量指针保存的地址),但是你可以改变篮子里面苹果的数目(可以通过指针改变指向的对象)
指向常量的常量指针:这就是上面两种的结合,好比你买了一个专门用来装10个苹果的篮子,那么这个篮子就不能用来装其他水果,同时也不能改变篮子里面苹果的数量。
修饰指针本身的const称为顶层const,修饰指针所指向对象的const称为底层const。
常量表达式:指不会改变且在编译过程就能得到计算结果的表达式,字面值属于常量表达式,用常量表达式初始化的常量对象也是常量表达式。一个值是不是常量表达式由他的数据类型和初始值决定
const int i=20;//常量表达式
const int j=i+2;//常量表达式;
int k=10;//不是常量表达式,因为k不是常量
const int z=get_size();//不是常量表达式,因为z的值只有运行时才能得到
2.5 处理类型
类型别名:
①使用关键字typedef定义类型别名
typedef double wages;//wages是double 的同义词,
wages pi=3.14;//类似于 double pi=3.14;
②使用c++11新标准别名声明using 来定义别名
using wave=double;
wave pi=3.14;//类似于 double pi=3.14;
auto类型说明符:可以让编译器通过分析表达式来推算变量类型,因此auto定义的变量必须有初始值,auto才能通过该初始值推出变量类型
int i=10,j=20;
auto a=i+j;//auto为int 型,auto变量必须有初始值,
auto在一条语句中声明多个变量时,所有变量类型必须一致,因为一条声明语句只能有一个基本数据类型
auto i=0,j=3.14;//错误,i和j的类型不一致
另外auto 会忽略掉顶层const,而保留底层const
decltype类型指示符:c++11新标准的说明符,其作用是选择表达式或函数的返回值类型,但是并不计算表达式和调用函数
decltype(f()) sum=x;//sum的类型就是函数f()返回值的类型,但是编译器并不调用f()函数。
第二章内容差不多就是这些,当然还有许多细节这里没有一一列出来,这章的重点还是引用和指针,想要了解更多内容还是得把书拿出来翻一翻。