我的《C++ primer》笔记第二章:变量和基本类型

文章目录

  • 写在前面
  • 2.1 基本内置类型
    • 2.1.1 算数类型
      • 带符号类型和无符号类型
      • 该如何在编程中选择我们要用的类型
    • 2.1.2 类型转换
      • 含无符号类型的表达式
      • 2.1.3 字面值常量
        • 转移序列
  • 2.2 变量
    • 2.2.1 变量定义
      • 初始化
      • 默认初始化
    • 2.2.2变量声明和定义的关系
    • 2.2.3 标识符
      • 变量命名规范
    • 2.2.4 名字的作用域
      • 嵌套作用域
    • 2.3 复合类型
      • 2.3.1 引用
        • 引用的定义
      • 2.3.2指针
        • 获取对象的地址
        • 指针值
        • 利用指针访问对象
        • 空指针
        • 赋值和指针
        • void指针
      • 2.3.3理解符合类型的声明
        • 定义多个变量
        • 指向指针的指针
        • 指向指针的引用
    • 2.4 const限定符
        • 初始化和const
        • 默认状态下,const对象仅在文件内有效
      • 2.4.1const引用
        • 初始化和对const的引用
        • 对const引用可能引用一个非const的对象
      • 2.4.2 指针和const
        • const指针
      • 2.4.3 顶层const
      • 2.4.4 constexpr和常量表达式
        • constexpr变量
        • 字面值类型
        • 指针和constexpr
    • 2.5 处理类型
      • 2.5.1类型别名
      • 2.5.2 auto类型说明符
        • 复合类型和常量
      • 2.5.3 decltype类型指示符
        • decltype和引用

写在前面

今天给大家讲述的是C++内置类型,并让大家理解下C++是如何支持更复杂的数据类型的。由于本人水平有限,内容难免会出现错误,如有错误,可以私信或者评论来指正我。如果你喜欢我的文章,可以留下一个大大的点赞=v=。

2.1 基本内置类型

2.1.1 算数类型

算数类型分为两种:整型和浮点型,字符型(char)和布尔类型(bool)包括在整型里面。

类型 含义
bool 布尔类型
char 字符
short 短整型
int 整型
long 长整型
longlong 长整型
float 单精度浮点型
double 双精度浮点型
long double 扩展进度浮点数

上述除了字符和浮点型外,存在一种C++约束,这种C++约束就是一个int类型至少和一个short类型大,一个long至少和一个int一样大,一个longlong类型至少比一个long大。

带符号类型和无符号类型

除了布尔类型和扩展字符型之外,其他类型都可以分为带符号和无符号类型。带符号顾名思义可表示正数、0、负数。不带符号则只能表示0、和整数。这些类型数在计算机里面表示的范围由计算机的位数决定,假设是32位计算机,8bit也就是1位char类型可表示的数值数为256个(2^8)无符号范围是0-255,有符号范围是-128-127。
带符号类型在代码中表示为:int
无符号类型在代码中表示为:unsigned int
在此处唯一特别的是char类型,char类型分为三类char、signed char、unsigned char。但是虽然有三种情况但表现形式只有两种,有符号字符和无符号字符。类型char 会表示char 和signed char 其中一种 ,具体是由编译器决定的。

该如何在编程中选择我们要用的类型

  • 1.当我们不知道数值不可能为负时,我们应当使用无符号型变量(unsigned)
  • 2.使用int时,如果数值范围超出应该用long long 而不是 long
  • 3.执行浮点数运算时,我们要多用double 类型 少用float 类型,原因有如下,首先在计算机运行过程中,double类型执行速度快于float,其次double进度高于float。

2.1.2 类型转换

当我们程序运行着运行着,我们使用的一种对象其实应该去另外一种类型时就会发生类型转换。

double i = 1.1;
int j = 0;
j = i;//此处发生类型转换 j = 1;

类型所能表示的值决定了转换的过程

  • 1.非布尔类型转成布尔类型,0转换为false,否则转换为true
  • 2.布尔类型转换成非布尔类型,true转换为1,flase转换为0
  • 3.浮点数转换成整型,只取浮点数小数点前部分,后面全部舍弃
  • 4.整型转成浮点型,小数部分为0,如果整数所占空间超浮点数类型的容量则会造成精读损失
  • 5.我们给无符号类型赋予超出他范围的值,该值会和该类型所能表示的数值数量取模运算,如 把-1赋给unsigned char(8比特大小)值会变成255(最大范围数为256(2^8))
  • 6.我们给一个有符号的值赋给他超出范围的值,结果是未定义的,可能程序还会继续工作,但也有可能程序会崩溃。

含无符号类型的表达式

我们在进行无符号类型表达式的运算时,一定要保证无符号类型最后不能是负数,如果为负数就会形成数值错误。

unsigned int x = 1;
int y = -2;
cout<< x + y << endl;//输出4,294,967,295(4,294,967,296 - 1)

2.1.3 字面值常量

字面值常量表示形式相对复杂,这里因为这只是快速上手,所以不予以讨论,此处只说明转义序列

转移序列

这是一类不可打印的字符,一般由\开始,如\n。

名字 表现形式
换行符 \n
横向制表 \t
响铃符 \a
纵向制表符 \v
退格符 \b
双引号 \"
反斜线 \\
问号 \?
单引号 \’
回车符 \r
进纸符 \f

2.2 变量

2.2.1 变量定义

变量一个具有自己名字以及有一块程序可以操作的内存空间的东西。
对于C+而言变量和对象一般可以相互使用。
那么何为对象呢?
对象是一块具有某种类型且能储存数据的内存空间。

初始化

对象在创建时获得了一个特定的值,就叫做对象的初始化,但是大部分人会说一开始进行了对象赋值,这里要说一下赋值和初始化是两种不同的操作!!初始化是在创建对象时对象获得的一个值,之前没有值。而赋值是对象原先具有一个值,现在将这个值去掉,赋予一个新的值。

默认初始化

在C++中存在着默认初始化,如果对象定义在函数外面,这个对象会被初始化为0,但是如果这个对象定义在函数里面,这个对象将会被赋予一个不确定的值。因此作为C++程序员的我们应当在对象创建的时候就进行人为初始化。

2.2.2变量声明和定义的关系

C++语言支持分离式编译,分离式编译可以理解成,把一个程序分为多个文件编写,每个文件单独编译。文件之间共享代码就需要用到extern来声明变量

extern int i ;

声明和定义不同,声明没有初始化,定义有初始化。在C++中如果在一个声明中初始化程序则会报错且变量只能定义一次但可以声明多次

2.2.3 标识符

C++标识符不限制长度,但对大小写敏感且不可以用C++保留的名字如int。

变量命名规范

  • 标识符要有具体含义
  • 变量名用小写字母,如index ,不能使用Index
  • 用户自定义的类名由大小字母开头,如Sales_item
  • 如果标识由多个字母构成,字母之间应有明显区分,如student_loan或者studentLoan

2.2.4 名字的作用域

我们定义的变量具有其作用区域,这块区域我们成为作用域
作用域分为全局作用域块作用域变量定义在函数外面称为全局变量,他的作用域为全局作用域。与之相反变量定义在函数里面成为局部变量,他的作用域成为块作用域。其生存周期取决于该块

int main()
{
	if(1)
	{
		int i = 1;//局部变量
		cout << i << endl;//成立
	}
	cout << i << endl; //显示i未定义
	return 0;
}	

嵌套作用域

我们在编辑程序过程时候应当避免使用到嵌套作用域,嵌套作用域的意思即为一个名字用于定义了全局变量,有用于定义了局部变量。这样很影响程序可读性,因为局部变量会覆盖全局变量。如果我们在一个程序中可能用到全局变量,那么他的名字就不要在定义成局部变量了,毕竟名字又不限制长度对吧!

2.3 复合类型

复合类型有两种引用指针

2.3.1 引用

C++11中引入了一种右值引用这里暂时不予以讨论。所以这里的引用为左值引用
引用可以理解成给对象起了另一个名字,这个名字是这个对象的别名,但是引用不是对象,对象在初始化时会将一个值拷贝给对象,但是引用初始化是和一个对象进行绑定,无法将一个值拷贝给引用,引用也无法拷贝他人。故所以,引用必须要初始化。
引用并非对象,相反的,它只是为一个已经存在了的对象起了一个别名

引用的定义
int i = 1;
int &j = i;
cout << j << endl; //输出为1

2.3.2指针

指针是另外一种复合类型,他的用处可以理解成“指向”某一对象。指针本身是一种对象,于是他和引用不同,他可以被赋值和拷贝,他的生命周期可以指向很多很多个对象,比如他现在指向了和他同一基本类型的A,下一秒他也可以指向和他同一基本类型的B。
指针通常比较难以理解,再有经验的程序员有时候对指针操作也会出现错误,因此我们要细心耐心的去学习指针

获取对象的地址

指针用于存放对象地址,想要获得对象地址,我们要用&符。

int i = 1;
int *j = &i;//获取i的地址
cout << j << endl; //打印的是i的地址

这里要说明一点,由于引用不是对象,他没有一块属于自己的存储空间,所以我们不能定义指向引用的指针,但是我们却可以定义引用指针的名字

int i = 1;
int *j = i;
int &k = *j;
cout << k << endl;//k的值为1;

注意:指针的类型一定要和它指向的对象相互匹配,也就是说,在这里无隐式类型转换

指针值

一般指针有如下四种状态:

  • 1.指向一个对象
  • 2.指向紧邻对象所占空间的下一段空间
  • 3.空指针,没有指向任何对象。
  • 4.无效指针,也可以被成为“野指针”他没有指向任何区域,十分危险!
    如果拷贝或者引用指向未知区域的指针将会引发错误,但是这类错误编译器无法识别出,所以我们要在定义指针的时候就要将它进行初始化!
利用指针访问对象

如果我们要访问指针存放指向对象内存区域的值的时候,我们可以用到指针操作符“*”,来访问该对象。

int i = 42;
int *j = &i;
cout << *j << endl;//输出42,而不是i的地址。
空指针

在C++11中,有字面值nullptr来给指针赋予空初始化,也就是指针为0,在C语言中也有编译器预定义NULL(预处理变量),当然我们也可以直接给指针初始化赋为0;

int *a = nullptr;
int *b = NULL;
int *c = 0;

这里再次强调!!定义指针的时候必须要初始化!!所以我们一定要形成一种定义变量时就进行变量初始化!

赋值和指针

指针和引用的区别之一是,一旦定义了引用就无法绑定了其他对象。引用十分专一,指针却在指向一个对象的时候,下一秒还可以不指向之前指的对象,指向一个新的对象。所以读到这里的你,你在感情方面是引用呢还是指针呢?
可能很多人会分不清,我究竟是改变的指针所指向区域,还是改变指针所指向区域的值呢?
很简单我们只需要看等号左边被操作数是什么如果是没有带星号*改变的是指向区域,带星号则改变你指向区域的值

 int i = 1;
 int *j = &i;
 cout << *j << endl;//输出为i的值->1
 int k = 3;
 *j = 2;
 cout << *j << endl;//输出2
 cout << i <<endl;//输出2 i值被改变
 j = &k;
 cout << *j << endl;//输出 3 j指向的区域发生改变
void指针

void 指针可以指向任何类型的对象,但是由于他不知道他具体指向什么对象,所以我们无法对它进行操作,所以void*只是存放内存区域的一块内存空间。

2.3.3理解符合类型的声明

变量组成包括了一个基本数据类型,一组声明符。一条语句中基本数据类型只有一个,但是声明符可以有很多。

定义多个变量

在定义多个变量时,我们应该具有比较好的编写习惯,不好的编写习惯会在逻辑上面比较难理解,甚至出错。

int* a  , b ;//a是整型指针,b是整型变量 

上面一条语句就容易让人误解成定义两个整型指针我们应该这样写

int *a,*b;

或者

int *a = nullptr;
int *b = nullptr;

笔者比较喜欢下面一种方式。

指向指针的指针

指针是可以分等级的,等级级别由声明符决定,

int *p1 = nullptr;
int **p2 =nullptr;
int i =1;
p1 = &i;
p2 = &p1;
cout << **p2 << endl; //输出1,解除两级引用
指向指针的引用

引用不是对象,所以指针不能指向引用。但是指针是对象,所以引用可以绑定指针。

int i = 1;
int *p = &i;
int *&j = p;
cout << *j << endl;//1
*j = 2;
cout << *j << endl;//2
cout << *p << endl;//2
cout << i << endl;//2

要理解j的类型具体是什么我们应该从右到左阅读j的定义,距离变量名最近的类型有最直接的影响,此处为&。所以他是指向指针的引用。

2.4 const限定符

const 用于修饰数据类型在定义之后不可被改变,于是const变量必须要定义的时候就初始化。

const int i = 1;
初始化和const

const的作用是保证该变量不能够在被定义后修改,但是却可以用const常量来赋值给变量,且具有相应的隐式转换。

const int i = 1;
int b = i;
bool c = i;
cout << b << endl;//1
cout << c << endl;//1
默认状态下,const对象仅在文件内有效

前面提到了extern可以让变量在不同文件内共享代码,const也不例外,const也只能定义一次,但可以在其他文件声明。只不过表示方式不同

extern const int i = 1;//定义了一个可在多文件内声明使用的常量

如果你想在多文件使用const,则需要在定义const时加上extren

2.4.1const引用

变量有引用,常量也有引用。

const int i = 1;
const int &j = i;
cout<< j <<endl;//1
初始化和对const的引用

const引用很特别她不仅可以绑定常量还可以绑定变量。

int i = 1;
const int &i1 = i ;
cout << i1 <<endl;//1 

之所以可以这样是因为C++编译器里面产生了一个临时量,通过临时量来初始化常量引用

int i = 1 ;
const int temp = i;//temp为临时量
const int &i1 = temp;
对const引用可能引用一个非const的对象

一个变量可被多个引用给绑定,被const引用绑定过的对象也可以被非const对象所引用。通过改变i的值也可以改变const引用的值,但C++语言规定这样做是非法的,因为大家基本上不会吧引用绑定到临时量上面

int i = 1;
int &j = i;
const int &j1 = i;
cout << j1 <<endl;//1
cout << j << endl;//1
cout << i <<endl;//1
j = 2;
cout << j1 <<endl;//2 通过j 改变了j1
cout <<j <<endl;//2
cout << i <<endl;//2

2.4.2 指针和const

与引用一样,指针也可以指向常量或者非常量。

int x = 1;
const int *p = &x;
cout << *p <<endl;//1

和常量引用一样,并没有规定右值是否为常量。

const指针

const指针有两种,一种是可以通过被引用的变量来改变指针的值,另一种不可以通过被引用的变量来改变指针的值。

int x = 1;
const int *p1 = &x;
cout << *p1 <<endl;//1
x = 2;
cout << x <<endl;//2
cout << *p1 << endl;//2
const int x = 1;
const int *const p1 = &x;
cout << *p1 <<endl;
x = 2;//出错不可被改变,程序停止编译
cout << x << endl;
cout << *p1 <<endl;

小技巧:前面说到,判断一个变量的类型,从右向左看,里变量名最近那个决定了这个变量的性质,此处 是const p1 ,离p1最近的是const 故所以不能通过改变x的值来改变p1的值

2.4.3 顶层const

  • 顶层const:表示指针本身是个常量,不可以用任何方式来改变这个指针的的值。即为顶层const不受什么影响。
  • 底层const:指针所指向的对象是一个常量。
int x = 1;
int *const p1 = &x;//顶层const
const int *p2 = &x;//底层const

指针类型既可以是顶层const也可以是底层const

const int x = 1;//顶层const
const int *const p1 = &x;//又是顶层const也是底层const

顶层const可以定义底层const,但是底层const不可以定义顶层const

int x = 1;
int *const p1 = &x;//顶层const
const int *p2 = p1;//底层const
cout << *p2 << endl;//1
int x = 1;
const int *p2 = &x;//底层const
int *const p1 = p2;//顶层const 出错,底层const不可以赋值给顶层const
cout << *p2 << endl;//1

2.4.4 constexpr和常量表达式

常量表达式,在编译过程(注意运行和编译的区别)中就能得到结果且不能被改变,用常量表达式初始化的const对象也是常量表达式。字面值属于常量表达式。

const int x = 20;//x是常量表达式
const int x1 = x + 1;//x1也是常量表达式
int t = 27;//不是常量表达式
const int t1 = get_size()//不是常量表达式,他的值得到运行get_size()函数时才可以得到。
constexpr变量

尽管我们可以用const来定义常量表达式,但是在实际使用中const很难区别出我们一开始究竟定义的是不是一个常量,于是C++11给了我们一个更好用的东西,constexpr变量。它定义出来的东西一定是一个常量。而且必须常量表达式初始化。

constexpr int my = 20;
constexpr int my_1 = my + 1;
constexpr int t = size()//当size是一个constexpr函数时成立
字面值类型

算数类型、引用、指针,都属于字面值类型可以被定义成constexpr,而自己定义的类、IO库、string类型不属于字面值类型,不可以被定义成constexpr。

指针和constexpr
const int *p =nullptr;//底层const
constexpr int *p = nullptr;//顶层const

注意:定义constexpr的时候,右值必须经过合法初始化

2.5 处理类型

2.5.1类型别名

类型别名:typedef ,用法 typedef 需要被起别名的东西 别名名字

typedef int zheng 
zheng i = 1; //i是整型变量

别名声明:using ,用法 using 别名 = 被起别名

using zheng = int ;
zheng i = 1;//i为整型

2.5.2 auto类型说明符

当我们编程越来越多,越难记得当前运算我们需要什么类型,于是在C++11中就有了auto类型说明符

double d1 = 1.1;
double d2 = 1.2;
auto x = d1 + d2;//x为整型
复合类型和常量

一般来讲auto和int之类差不多,且&和*在算数类型中只服务于声明符而并不是基本数据类型的一部分,
所以再用auto去弄复合类型或者常量时有几点需要注意

  • 1.auto一般会忽略掉顶层const,底层const则会被保留下来
  • 2.定义引用
int i = 1;
auto & j = i;//j是整型引用

也就是说auto只决定基本数据类型,如需复合数据类型则需自己添加

2.5.3 decltype类型指示符

在我们有些时候,我们想从表达式类型判断出要定义的类型,但是不想用该表达式的值初始化变量。C++11给出了我们decltype类型指示符。

int x = 1;
decltype(x) a = 2;//不用x=1只用x的类型,a是整型

decltype处理顶层const与引用的方式不同,decltype不忽略顶层const和引用。就是说我们不用重新使用const 、&也可以定义const或者&类型。

decltype和引用

使用decltype定义引用必须初始化!
且在decltype使用有一个需要注意的地方

	int x = 1;
	int& x1 = x;
	decltype((x1)) a = x; //a是整型引用。
	cout << a << endl;//1
	x = 2;
	cout << a << endl;//2

切记,decltype((variable))结果永远是引用!如果只有一个括号,则括号内是引用,定义的类型才是引用

创作不易,感谢您能看完。如果您喜欢,可以在线赞赏鼓励我,您的鼓励是我前行的动力!如需转载,请私信我,谢谢!

你可能感兴趣的:(费曼学习法,c++)