C++Primer读书笔记——2.变量和基本类型-创新互联

变量和基本类型 一、基本数据类型

C++定义了一套包括算术类型(arithmetic type) 和 空类型(void)在内的基本数据类型。算数类型包括字符型、整型、布尔型和浮点型。空类型不对应具体的值,大多数情况下当函数不返回任何值的时候作为返回类型。

创新互联是专业的随县网站建设公司,随县接单;提供成都做网站、网站设计,网页设计,网站设计,建网站,PHP网站建设等专业做网站服务;采用PHP框架,可快速的进行随县网站开发网页制作和功能扩展;专业做搜索引擎喜爱的网站,专业的做网站团队,希望更多企业前来合作!1.算术类型

算数类型分为两类:整型(字符型、布尔型,整型)和 浮点型。不同类型所占字节的大小在不同的机器上有所差异

类型含义最小尺寸
bool布尔类型未定义
char字符8位
wchar_t宽字符16位
char16_tUnicode 字符16位
char32_tUnicode 字符32位
short短整型16位
int整型16位
long长整型32位
long long长整型64位
float单精度浮点数6位有效数字
double双精度浮点数10位有效数字
long double拓展精度浮点数10位有效数字
  • 布尔型的取值是真(true)或假(false)
  • C++提供的字符型大部分都支持国际化。基本字符类型是char,一个char可以存放基本字符集中任意字符对应的数值
  • 其他字符用于拓展字符集,如wchar_t,char16_t,char32_t
  • C++规定一个int至少和一个short一样大,一个long至少和一个int一样大,一个long long 至少和一个long 一样大
  • 浮点数可以表示单精度,双精度和拓展精度。一般来说float 和 double 分别有7位和16位有效数位。long double 则通常被用于有特殊浮点要求的硬件

有符号类型和无符号类型

  • 出去布尔型和拓展的字符型外,其他整型可以划分为有符号的(signed) 和 无符号的(unsigned)两种。有符号数可以表示正数,负数和0。无符号数仅表示大于等于0的数。
2.类型转换

对象类型定义了对象能包含的数据和能参与的运算,其中一种运算被大多数类型支持,就是将对象从一种给定的数据类型转换为另一种相关类型。

bool b = 42; // b 为true
	int i = b; // i 为 1
	i = 3.14;  // i 为 3
	double pi = i;  //pi 为 3.0
	unsigned char c = -1; //c 为 255
	signed char c2 = 256;//c2 未定义

含有无符号类型的表达式:

unsigned u = 10;
	int i = -42;
	cout<< i + i<< endl; //84
	cout<< u + i<< endl; //4294967264   即 -32 的二进制表示 看作无符号数 就是4294967264

当一个表达式中既有无符号数又有int值时,这个int值就会转换成无符号数。负数转换为无符号数类似于直接给无符号数赋一个负值。

**注意:**如果在循环中使用无符号数,可能会造成死循环

//会一直死循环下去
	for (unsigned u = 10; u >= 0; u--) {cout<< u<< endl;
	}
3.字面值常量

一个形如42的值被称为字面值常量,这样的值一看就知。每一个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。

  1. 整型字面值

我们可以将整型字面值写作10进制数,8进制数 或 16进制数。默认情况下 10进制字面值是有符号数,8进制数 和 16进制字面值既可能是有符号数也可能是无符号数。short没有对应的字面值

int a = 1;//10进制数
	int b = 02;//8进制数
	int c = 0x123;//16进制数
  1. 字符和字符串字面值

由单引号括起来的是char字面值,双引号括起来的零个或多个字符称为字符串字面值。字符串字面值的类型实际上是由常量字符构成的数组array。

char c = 'a'; //字符字面值

string s = "abcd"; //字符串字面值
  1. 布尔型和指针字面值

true 和 false 是布尔型的字面值。nullptr 是 指针字面值。

bool ok = true;

int * ptr = nullptr;
二、变量

变量提供一个具有名字的,可供程序操作的内存空间。C++中每一个变量都有其数据类型,数据类型决定着变量所占内存空间的大小和布局方式,该内存空间能存储值的范围,以及变量能参与的运算。

1. 变量定义

形式:类型说明符 + 一个或者多个变量名组成的列表(其中变量名以逗号分隔)

什么是对象?

通常情况下,对象是指一块能存储数据 并且 具有某种类型的内存空间

1.初始值

当对象在创建时获得了一个特定的值,这个对象就被初始化了。
在C++中,初始化是一个非常复杂的问题,实际上初始化和赋值是两个完全不同的操作。初始化不是赋值,初始化的含义是创建对象时赋予其一个初始值,而赋值的含义是把对象的当前值擦除,而以一个新的值来替代。

2.列表初始化

作为C++ 11 的标准,可以使用花括号来初始化变量,这种初始化形式称为列表初始化。

//以下四种方式都可以初始化
	int a = 10;
	int b(10);
	int c = {10 };
	int d{10 };

注意:如果使用列表初始化 并且 初始化存在丢失信息的风险时,编译器会报错

 long double ld = 3.14159;

	int a{ld }, b = {ld }; //编译器报错

	int c(ld), d = ld;  //正常执行,丢失了小数部分的精度
3.默认初始化

如果定义变量的时候没有指定初始值,则变量被默认初始化,此时变量也被赋予默认值。

  • 定义于任何函数体之外的变量都被初始化为0
  • 定义在函数体内部的内置类型变量将不会被初始化。一个未初始化的内置类型变量的值是未定义的,如果试图拷贝或以其他的方式访问这个值将会发生错误
  • string 类规定如果没有指定初始值则生成一个空串
  • 一些类要求每一个对象都显式初始化,此时如果创建了一个该类的对象而未对其做初始化操作,将引发错误

建议初始化每一个内置类型的变量

2.变量声明和定义的关系

C++支持分离式编译机制,该机制允许将程序分割为若干个文件,每一个文件都可以被独立编译。

  • 为了支持分离式编译,C++将声明和定义区分开。声明使得变量名为程序所知,一个文件如果想使用其他地方定义的变量则必须包含对那个变量的声明。定义负责创建与变量名关联的实体。
  • 定义会申请存储空间,也可能为变量赋一个初始值。

如果想声明一个变量而不是定义,可以在变量名前面添加 extern ,并且不要显式的初始化变量

extern int a; //声明 a

	int b; //声明 并且 定义 b

extern 语句如果包含初始值就不再是声明了,而变成定义。如果在函数体内部,初始化一个由extern标记的变量,会引起错误:

void test2() {extern int a; //声明 a

	int b; //声明 并且 定义 b

	extern int c = 20; //报错
}

注意:变量只能被定义一次,但是可以被声明多次

如果要在多个文件中使用同一个变量就必须将声明和定义分开。变量的定义只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,绝对不能重复定义。

C++是一种静态类型语言,其含义是在编译阶段检查类型。其中,检查类型的过程称为类型检查。

3.标识符

C++的标识符由字母、数字和下划线组成,其中必须以字母或者下划线开头。用户自定义的标识符中不能连续出现两个下划线,也不能以下划线紧邻大写字母开头。

变量名定义规范:

  • 变量名要能体现其实际含义
  • 变量名一般用小写字母
  • 用户自定义的类名以大写字母开头
  • 如果变量名由多个单词组成,则单词之间应该有明显区分
4.变量名的作用域

作用域是程序的一部分,在其中变量名有其特定的含义。C++中大多数作用域都以花括号分隔。

变量名的有效区域 始于变量名的声明语句,以声明语句所在的作用域末端为结束。

如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量

三、复合类型

复合类型是指基于其他类型定义的类型。本节介绍其中的两种:引用 和 指针

1. 引用

引用为对象去了另一个名字,引用类型引用另外一种类型(这里的引用指的是左值引用)。定义引用时,程序把引用和它的初始值绑定到一起,而不是将初始值拷贝给引用。

int val = 10;
	int& refval = val; //refval 是 val的另外一个名字

注意:引用并不是对象。相反,他只是为一个已经存在的对象所起的另外一个变量名。因为引用不是对象,所以不能定义引用的引用

1.引用的定义

允许再一条语句中定义多个引用,其中每一个引用标识符都必须&开头。除了两种特殊情况外,其他所有引用的类型都要和与之绑定的对象严格匹配。并且,引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定到一起。

int& ref = 10;  //错误:引用类型的初始值必须是一个对象

	double dval = 3.14;
	int& ref2 = dval; //错误:此处引用类型的初始值必须是int类型的
2.指针

指针是指向另一种类型的复合类型。和引用类似,指针也实现了对其它对象的间接访问。不同点:指针本身也是一个对象,允许对指针进行赋值和拷贝操作,而且在指针的生命周期内他可以先后指向几个不同的对象;指针不用再定义的时候赋初始值。

1.获取对象的地址

指针存放某个对象的地址,要想获取该地址,需要使用取地址符&:

int a = 10;
	int* p = &a; //p存放变量a的地址  或者说 p 是指向变量a 的指针

因为引用不是对象,没有实际地址,所以不能定义指向引用的指针

除了两种特殊的情况外,其他所有指针的类型都要和它所指向的对象的类型匹配:

double dval = 3.14;
	double* pd = &dval; 
	double* pd2 = pd;

	int* pi = pd;  //错误:指针pi的类型和pd的类型不匹配
	pi = &dval;    //错误:试图把double类型对象的地址赋给int型指针
2.指针值

指针的值(地址)应该属于以下4种状态之一:

  • 指向一个对象
  • 指向紧邻对象所占空间的下一个位置
  • 空指针,意味着指针没有指向任何对象
  • 无效指针,上述三种情况之外的其他值

试图拷贝 或者以其他的方式访问无效指针的地址都会引发错误

虽然第二种和第三种指针的形式是有效的,但是这些指针没有指向任何具体的对象,所以试图访问此类指针的对象的行为是不被允许的

3.利用指针访问对象

如果指针指向了一个对象,则允许使用 解引用符 * 来访问该对象:

int a = 42;
	int* p = &a;
	cout<< *p<< endl;  //由* 得到指针p指向的对象,即42

注意:解引用操作仅适用于那些确定指向了某个对象的有效指针

4.空指针

空指针(null pointer) 不指向任何对象,在试图使用一个指针之前可以先判断它是否为空

int* p1 = nullptr;
	int* p2 = 0; //直接将p2 初始化为字面常量0

	int* p3 = NULL; //等价于 int *p3 = 0;

推荐使用 nullptr 初始化空指针

把 int 变量直接赋值给指针是错误操作,即使这个int 变量的值恰好为0也不行

int zero = 0;
	int* pi = zero; //错误:不能把int 变量直接赋值给指针

建议初始化所有指针

5.赋值和指针

指针和引用都能够间接的访问其他对象,但是二者又有很大的不同,其中最重要的一点就是引用并不是一个对象。一旦定义了引用,就不能将其再绑定到另外的对象上。指针和它存放的地址之间就没有这种限制了。

int a = 10;
	int* p1 = 0;//p1初始化为空指针 不指向任何对象
	int* p2 = &a;//p2 被初始化 指向 a
	int* p3;     //p3 没有被初始化

	p3 = p2;     //现在 p2 和 p3 指向同一个对象 a
	p2 = 0;      //现在p2 不指向任何对象了
6.其他指针操作
  • 只要指针拥有一个合法值,就能够将它用在条件表达式种。如果指针的值是0,条件为false;任何非0指针对应的条件值都是true
  • 如果两个指针存放的地址相同,则它们相等,反之则不相等
7.void* 指针

void* 是一种特殊的指针类型,可用于存放任意对象的地址。一个void*指针存放一个地址,这一点和其他指针类似。不同的是,我们不清楚这个指针指向什么类型的对象。

double a = 3.14, * p = &a;

	void* pv = &a;    //a 可以是任意类型的对象
	pv = p;           //pv 可以是任意类型的指针

void指针的功能比较少,不能直接操作void指针所指的对象,因为我们并不知道他这个对象是什么类型。

概括说来,以void* 视角来看内存空间也就只是内存空间,没办法访问内存空间中的所有对象。

3.理解复合类型的声明 1.定义多个变量

经常会有一种观点,在定义语句时,类型修饰符(* 或 &)作用于本次定义的全部变量

int* p1, p2;  //p1 是指向int的指针,p2 是int
2.指向指针的指针

指针也是内存中的对象,也有自己的地址,因此允许把指针的地址放到另一个指针当中,即允许另外一个指针指向指针。

int a = 10;
	int* p = &a; //p 指向一个 int 类型的数
	int** pp = &p; //pp 指向一个 int 类型的指针

	//要访问最原始的那个对象,需要对指针的指针做两次解引用
	cout<< **pp<< endl; //a 的值
3.指向指针的引用

引用本身不是一个对象,因此不能定义指向引用的指针。但指针是对象,所以存在对指针的引用

int a = 10;
	int* p;

	int*& r = p; // r 是一个对指针 p 的引用

	r = &a; //r 引用了一个指针 因此给 r 赋值 &a ,就是让指针 p 指向 a
	*r = 0; //让 p 指向的对象 也就是 a 的值变为0
四、const限定符

有时我们希望第一这样的一种变量,它的值不能够被改变,可用const对变量的类型加以限定。因为const对象一旦创建后,其值就不能够再修改,所以const对象必须初始化

const int a = get_size();// 运行时初始化
	const int b = 42;//编译时初始化
	const int c; //错误:未初始化
1.初始化和const

可以利用普通变量去初始化const变量

int a = 42;
	const int b = a; //正确
	int c = a;      //正确

拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没什么关系了。

2.默认情况下,const对象仅在文件内有效

编译器将在编译过程中把用到该变量的地方都替换为对应的值。

为了避免对同一个变量的重复定义。默认情况下,const对象被设定为仅在文件内有效。当多个文件出现了同名的const变量时,其实等同于在不同的文件中分别定义了独立的变量。

如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern。

1.const的引用

可以把引用绑定到const对象上,即对常量的引用。与普通引用不同的是,对常量的引用不能修改它所绑定的值。

const int a = 1024;
	const int& r = a;   //正确

	r = 20;            //错误:r是对常量的引用 不能改变
	int& r2 = r;       //错误:试图让一个非常量引用 指向 一个常量对象
1.初始化和对常量的引用

之前提到的 引用类型 必须和其所引用的 对象类型 一致,但是有两种例外。其中一种就是在初始化常量引用时允许用任意表达式作为初始值,只要表达式的结果能转换成引用的类型即可。

int a = 10;
	const int& r1 = a;         //正确:允许将一个const int& 绑定到一个普通int对象上
	const int& r2 = 42;        //正确
	const int& r3 = r1 * 2;    //正确
	int& r4 = r1 * 2;          //错误:r4 是一个普通的非常量引用
2.对const的引用,可能用一个非const的对象

常量引用仅仅对可参与的操作做了限定,对于引用的对象本身是不是常量并未做限定。因为对象也可能是一个变量,所以允许通过其他途径改变它的值。

 int a = 10;
	const int& r1 = a;         //正确:允许将一个const int& 绑定到一个普通int对象上
	const int& r2 = 42;        //正确
	const int& r3 = r1 * 2;    //正确
	int& r4 = r1 * 2;          //错误:r4 是一个普通的非常量引用
2.指针和const

与引用一样,也可以让指针指向常量或者变量。类似于常量引用,指向常量的指针不能改变其指向的值。想要存放常量对象的地址,只能使用指向常量的指针。

const int a = 5; //a 是一个常量 它的值不能改变
	int* ptr = &a;   //错误: ptr 是一个普通指针

	const int* cptr = &a; //正确: cptr 可以指向一个整型常量
	*cptr = 42;           //错误: 不能给 *cptr 赋值

之前提到过,指针的类型必须与所指向的对象类型一致,其中一种例外是允许一个指向常量的指针指向变量。

int a = 10;
	const int* cptr = &a; //正确:但是不能通过 cptr 改变 a 的值

和常量引用一样,指向常量的指针也没有规定所指向的对象必须是常量。

1.const指针

指针是对象而引用不是,因此可以把指针本身定义为常量。常量指针必须初始化,一旦初始化,它的指向就不能改变了。

int a = 10;
	int* const p = &a;   // 指针 p 就一直指向 a了,不能改变其指向了

指针本身是一个常量,也可以通过指针去修改其指向对象的值,只是指针的指向不能改变而已

3.顶层const

顶层const表示指针本身是一个常量,底层const表示指针所指向的对象是一个常量。

更一般的,顶层const可以表示任意对象是常量,这一点对任何数据类型都适用。

底层const则与指针和引用等复合类型有关。比较特殊的是:指针既可以是顶层const,也可以是底层const

int a = 0;
	int* const p1 = &a; //不能改变 p1 的值,顶层const
	const int ci = 42; //不能改变 ci 的值,顶层const
	const int* p2 = &ci;// 允许改变 p2 的值 , 底层const
	const int* const p3 = p2; //右边时顶层const , 左边的是底层const
	const int& r = ci; //声明引用的const 都是底层const

当执行对象拷贝操作时,常量是顶层const 还是 底层const区别明显。顶层const不受影响:

a = ci;   //ci 是一个顶层const 对此操作无影响
	p2 = p3;  //p2 p3 指向的对象类型相同,p3 顶层 const 的部分不受影响

执行拷贝操作并不会改变对象的值,因此拷入拷出的对象是不是常量都没什么影响

底层const的限制不能忽视。当执行拷贝操作时,拷入拷出的对象必须具有相同的底层const,或者两个对象的数据类型能够转换。变量可以转为常量,反之不行。

int* p = p3; //错误: p3 有底层const p没有
	p2 = p3; //正确: p2 和 p3 都是底层 const
	p2 = &a; //正确: int* 能转换为 const int*
	int& r = ci; //错误:普通的int& 不能绑定到 int常量上
	const int& r2 = a; //正确: const int& 可以绑定到一个普通int上
4.constexpr和常量表达式

常量表达式是指值不会改变并且在编译过程就能得到计算结果的表达式。显然字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。

一个对象是不是常量表达式由它的数据类型和初始值共同决定。

const int max_files = 20; //是
	const int limit = max_files + 1;  //是
	int staff_size = 27; //不是常量表达式
	const int sz = get_size(); //不是常量表达式 get_size 的具体值 需要到运行时才能获取到,所以不是
1.constexpr 变量

C++ 11 标准规定,允许将变量声明为constxepr类型以便由编译器来验证变量的值是否为一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化

constexpr int mf = 20;   //20 是常量表达式
	constexpr int limit = mf + 1; // mf + 1 是常量表达式
	const int sz = size();   // 只有当 size 是一个 constxepr函数时,才正确

一般来说,如果你认定一个变量是常量表达式,那就把它声明为constexpr类型

2.字面值类型

常量表达式的值需要编译时就得到计算,因此声明constexpr时 用到的类型必须有所限制。

算数类型,引用和指针都属于字面值类型。

尽管指针和引用都能定义成constexpr,但是它们的初始值却受到严格限制。一个constexpr指针的初始值必须为nullptr 或 0,或者是存储与某个固定地址中的对象。

函数体内部的变量一般来说并非存放在固定地址中,定义于函数体之外的对象地址固定不变,能用来初始化constexpr指针。

3.指针和constexpr

在constexpr声明中,如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指向的对象无关系。

const int* p = nullptr; //p 是一个指向整型常量的指针
	constexpr int* q = nullptr; // q 是一个指向 整数的常量指针

constexpr把它所定义的对象置为了顶层const

五、处理类型 1.类型别名

类型别名(type alias) 是一个名字,他是某种类型的同义词。

有两种方式可用于定义类型别名。传统的方式是使用关键字typedef

typedef double wages; //wages 是 double 的同义词
	typedef wages base, * p; //base 是 double 的同义词,p 是 double* 的同义词

C++ 11 标准规定了一种新的方法,使用别名声明(alias declaration) 来定义类型别名

using PII = pair; //PII 是 pair的同义词

建议使用新标准的方法

1.指针、常量和类型别名

如果某一个类型别名指代的是复合类型或者常量,那么把他用到声明语句中就会产生意外的结果。

typedef char* pstring;
	const pstring cstr = 0;    //cstr 是一个指向char 的常量指针
	const pstring* ps;         //ps 是一个指针 ,它的对象是指向char的 常量指针
2.auto 类型说明符

C++ 11 标准引入了auto,auto能让编译器通过初始值来推算变量类型。显然,auto定义的变量必须有初始值。

int a = 10, b = 20;
	auto c = a + b; //c 也是 int 类型
1.复合类型、常量和auto

编译器推断出来的auto类型有时候和初始值的类型并不完全一样,编译器会适当改变结果类型使其更符合初始化规则。

使用引用其实是使用引用的对象,特别是当引用被用作初始值时,真正参与初始化的时引用对象的值。

int a = 20, & r = a;
	auto b = r;  // b = 20

auto 一般会忽略顶层const,保留底层const。

设置一个类型为auto的引用时,初始值中的顶层const常量属性仍然保留。

3.decltype 类型指示符

有时候会遇到这种情况:希望通过表达式的类型推断出要定义的变量类型,但是不想用该表达式的值初始化变量。为了满足这一要求,C++ 11 标准引入了decltype,作用是选择并返回操作数的数据类型。编译器会分析表达式得到它的类型,却不实际计算表达式的值。

decltype(fun()) sum = 10; // sum 的类型就是 函数fun 的返回类型(编译器实际上并不会调用fun函数)

与auto不同的是,如果decltype使用的是一个变量,则decltype返回改变量的类型(包括顶层const和引用在内)

const int ci = 0, & cj = ci;
	decltype(ci) x = 0;   //x 的类型是 const int
	decltype(cj) y = x;   //y 的类型是 const int& ,y 绑定到变量 x
	decltype(cj) z;       //错误: z是一个引用,必须初始化
1.decltype 和 引用

如果decltype 使用的是一个表达式,则decltype 返回表达式结果对应的类型。

另一方面,如果表达式内容是解引用操作,则decltpye会得到引用类型。

int i = 42, * p = &i, & r = i;
	decltype(r + 0) b; //r 是int&类型 , r + 0 就是 int 类型
	decltype(*p) c;    //错误: c 是 int&类型 , 需要初始化

如果变量名加上了一对括号,则得到的类型与不加括号时有所不同。如果给变量名加了一层或多层括号,编译器会把它当成时一个表达式。

//decltype 的表达式如果是加上了括号的变量 结果将是引用
	decltype((i)) d; //错误: d 是 int& 类型 , 需要初始化
	decltype(i) e;

注意:decltpye((var)) 注意是双层括号,结果永远是引用。而decltype(var) 结果只有当var本身是一个引用时才是引用。

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


文章标题:C++Primer读书笔记——2.变量和基本类型-创新互联
地址分享:http://hbruida.cn/article/cdhogp.html