《C++11Primer》阅读随记 -- 二、变量和基本类型

第二章 变量和基本类型

变量

  1. std::cin>>int input_value 报错

    输入运算符的工作是将输入流istream中的数据赋值给>>右边的变量,而对变量赋值并不是初始化工作。也就是说该变量未经初始化。

  2. 何为对象

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

变量声明和定义的关系

  • C++语言支持分离式编译( separate compilation ), 其实就是让不同文件相互之间联系起来,文件之间共享代码。而我们的西加加为了支持分离式编译,C++语言将声明和定义区分开勒。**声明( declaration )使得名字为程序所知,一个文件如果向使用别处定义的名字则必须包含对那个名字的声明。而定义(definition)**负责创建与名字关联的实体
  • 变量声明规定了变量的类型和名字,在这一点定义与之相同。但是除此以外,定义还申请存储空间,也可能会为变量赋一个初始值。
  • 如果想要声明一个变量而非定义它,就在变量名前添加关键字 extern, 而且不要显示地初始化变量
  • 任何包含了显示初始化地声明即成为定义

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

extern int i; // 声明 i 而非定义
int i; 		  // 声明并定义
extern double pi = 3.1416 // 定义 

作用域

#include <iostream>
int reused = 42;
int main(){
	int unique = 0; // unique 拥有块作用域
	// 输出: 使用全局变量 reused; 输出 42 0
	std::cout << reused << " " << unique << std::endl;
	int reused = 0; // 新建局部变量 reused, 覆盖全局变量 reused
	// 输出:使用局部变量 reused; 输出 0 0 
	std::cout << reused << " " << unique << std::endl;
	// 输出:显示地访问全局变量 reused; 输出 42 0
	std::cout << ::reused << " " << unique << std::endl;
	return 0;
}

复合类型

引用

引用(reference) 为对象起了另一个名字,引用类型引用(refers to)另一个类型。通过将生命夫协程 &d 的行是来定义引用类型,其中 d 是声明的变量名。

int ival = 1024;
int &refVal = ival; // refVal 指向 ival ( 是 ival 的另一个名字 )
int &refVal2; // 报错: 引用必须被初始化

引用即别名:引用并非对象,相反,它只是为一个已经存在的对象所起的别名,也是一种绑定(bind)的关系。对其进行的所有操作都是在与之绑定的对象上进行的

引用只能在定义时被初始化一次

int i = 10;
int j = 11;
int & a = i;

cout << "a = " << a << endl;
cout << "i = " << i << endl;
a = j;//注意,这里不是被使用j的别名,是i的值被赋值成j.
cout << "a = " << a << endl;
cout << "i = " << i << endl;

指针

指针和引用的不同点:

  1. 指针本身就是一个对象,允许对指针赋值和拷贝,而且在指针的声明周期内,它可以先后指向几个不同给的对象
  2. 指针无须再定义时赋初值。
int i = 42;
int &r = i;		// &紧随类型名出现,因此是声明的一部分,r是一个引用
int *p;			// *紧随类型名出现,因此是声明的一部分,p是一个指针
p = &i;			// &出现在表达式中,是一个取地址符
*p = i;			// *出现在表达式中,是个解引用
int &r2 = *p;	// &是声明的一部分,*是一个解引用符

建议初始化所有指针
在大多数编译器环境下,如果使用了未经初始化的指针,则该指针所占内存空间的当前内容将被看作一个地址值。访问该指针,相当于去访问一个本不存在的位置上的本不存在的对象。糟糕的是,如果指针所占内存空间恰好由内容,而这些内容又被当作了某个地址,我们很难分清它到底是合法的还是非法的了。

void* 指针 是一种特殊的指针类型,可用于存放任意对象的地址。从 void* 的视角来看,内存空间仅仅也就是内存空间,没办法访问内存空间所存的对象。

指针和引用的区别

  1. 指针有自己的一块空间,而引用指示一个别名
  2. 使用 sizeof 查看一个指针的大小是 4,而引用则是被引用对象的大小
  3. 指针可以被初始化 NULL, 而引用必须被初始化且必须是一个已有对象的引用
  4. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变所指向的对象
  5. 可以有 const 指针,但是没有 const 引用
  6. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能被改变
  7. 指针可以有多级指针(**p), 而引用只有一级
  8. 指针和引用使用 ++ 运算符的意义不一样
  9. 如果返回动态内存分配的对象或内存,必须使用指针,引用可能引起内存泄漏

指向指针的引用

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

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

r = &i;				// r 引用了一个指针,因此对 r 赋值 &i 就是令 p 指向 i
*r = 0;				// 解引用 r 得到 i, 也就是 p 指向的对象,将 i 的值改为 0

要理解 r 的类型到底是什么,最简单的办法是从右向左阅读 r 的定义。离变量名最近的符号(此例中是 &r& ) 对变量的类型有最直接的影响,因此 r 是一个引用。声明符的其余部分用以确定 r 引用的类型是什么,此例中的符号 * 说明 r 引用的是一个指针。最后,声明的基本数据类型部分指出 r 引用的是一个 int 指针

这一部分一下子让我对 常量指针和指针常量怎么分辨清晰了
const int* p = &i --> p 的指向可以更改,p 指向的值不可以更改。因为 * 离变量名 p 最近,所以说明 p 是一个指针。接着声明的基本数据类型被 cosnt 修饰 const int,说明 p 指向的是一个 int 常量。所以指向的值是不能改变的,但是 p 本身可以改变

int* const p = &i–> p 指向不可以更改,但是 p 指向的值可以更改。因为 const 离变量名 p 最近,说明这个变量 p 是常量,不能更改,接着符号 * 说明变量 p 是一个指针,那么指针是常量,就说明指针的方向不能更改。最后,声明的基本数据类型部分指出常量指针 p 是一个 int 指针

const 限定符

const 对象一旦创建后其值就不能再改变,所以 const 对象必须初始化。

默认状态下,const 对象仅在文件内有效

当以编译时初始化的方式定义一个 const 对象时,就如对 bufsize 的定义一样
const int bufSize = 512; // 输入缓冲区的大小
编译器将在编译过程中把用到该变量的地方都替换成对应的值。也就是说,编译器会找到代码中所有用到 bufSize 的地方,然后用 512 替换。

为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了 const 对象的文件都必须得能访问到它得初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为尽在文件内有效。当多个文件中出现了同名的 const 变量时,其实等同于在不同文件中分别定义了独立的变量

某些时候有这样一种 const 变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。这种情况下,我们不希望编译器为每个文件分别生成独立的变量。相反,我们想让这类 const 对象像其他(非常量)对象一样工作,也就是说,只在一个文件中定义 const ,而在其他多个文件中声明并使用它

解决的办法就是, 对 const 变量不管是声明还是定义都添加 extern 关键字,这样只需定义一次就可以了

// file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h 头文件
extern const int bufSize; // 与 file_1.cc 中定义的 bufSize 是同一个

如上述程序所示,file_1.cc 定义并初始化了一个 bufSize
file_1.h 头文件中的声明也由 extern 做了限定,其作用是指明 bufSize 并非本文件独有,它的定义在别处

constexpr 和 常量表达式

常量表达式(const expression) 是指指不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的 const 对象也是常量表达式。
一个对象( 或表达式 )是不是常量表达式由它的数据类型和初始值共同决定

const int max_files = 20;		// max_files 是常量表达式
const int limit = max_files + 1;	// limit 是常量表达式
int staff_size = 27;			// staff_size 不是常量表达式
const int sz = get_size();		// sz 不是常量表达式

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

constexpr int mf = 20;			// 20 是常量表达式
constexpr int limit = mf + 1;	// mf + 1 是常量表达式
constexpr int sz = size();		// 只有到 size 是一个 constexpr 函数时
								// 才是一条正确的声明语句 

到目前为止接触的数据类型种,算数类型、引用和指针都属于字面值类型。自定义类 Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成 constexpr
在这里插入图片描述

指针和constexpr

constexpr 声明种如果定义了一个指针,限定符 constexpr 仅仅对指针有效,与指针所指对象无关

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

p 和 q 的类型相差甚远,p 是一个指向常量的指针,而 q 是一个常量指针,其中的关键在于 constexpr 把它所定义的对象置为了顶层 const

类型别名

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

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

新标准规定了一种新的方法,使用 别名声明( alias declaration ) 来定义类型的别名:
using SI = Sales_item; // SI 是 Sales_item 的同义词

decltype 类型指示符

有时会希望从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量。为了满足这一要求, C++11 新标准引入了第二种类型说明符 decltype ,它的作用是选择并返回操作数的数据类型。在此过程中,编译器分析表达式并得到它的类型,但不实际计算表达式的值:
decltype(f()) sum = x; // sum 的类型就是函数 f 的返回类型
编译器并不实际调用函数 f, 而是使用当调用发生时 f 的返回值类型作为 sum 的类型。

auto 和 decltype 的差别

// auto 
int i = 0, &r = i;
const int ci = i, &cr = ci;
auto b = ci;		// b 是一个整数( ci 的顶层 const 特性被忽略了 )
auto c = cr;		// c 是一个整数( cr 是 ci 的别名,ci 本身是一个顶层 const )
auto d = &i;		// d 是一个整型指针( 整数的地址就是指向整数的指针 )
auto e = &ci;		// e 是一个指向整数常量的指针( 对常量对象取地址是一种底层 const )

//decltype
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 是一个引用,必须初始化

decltype 和 引用

decltype((variable)) 的结果永远是引用
decltype(variable) 的结果只有当 variable 本身就是一个引用时才是引用

预处理器概括

确保头文件多次包含仍能安全工作的常用计数时预处理器( preprocessor ), 它由 C++ 语言从 C 语言继承而来。预处理器是在编译之前执行的一段程序,可以部分地改变我们所写地程序。比如最常用地 #include, 当预处理器看到 #include 标记时就会用指定地头文件地内容代替 #include

C++ 程序还会用到的一项处理功能是 头文件保护符( header guard ), 头文件保护符依赖于预处理变量。预处理变量由两种状态:已定义和未定义。
#define 指令把一个名字设定为预处理变量,另外两个指令则分别检查某个指定的预处理变量是否已经定义:#ifdef 当且仅当变量已定义时为真, #ifndef 当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直到遇到 #ndef 指令为止

使用这些功能就能有效地防止重复包含地发生:

#ifndef SALES_DATA_H
#define SALES_DATA_H
#include <string>
struct Sales_data{
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
};
#endif

第一次包含 Sales_data.h 时,#ifndef 的检查结果为真,预处理器将顺序执行后面的操作直至遇到 #endif 为止。此时,预处理变量 SALES_DATA_H 的值将变为已定义,而 Sales_data.h 也会被拷贝到我们的程序中来。后面如果再一次包含 Sales_data.h#ifndef 检查结果为假,编译器将忽略 #ifndef 到 #endif 之间的部分

头文件即使没有被包含在任何其他头文件中,也应该设置保护符。头文件保护符很简单,程序员只要习惯性地加上就可以,没必要在意程序到底需不需要

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Artintel

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

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

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

打赏作者

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

抵扣说明:

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

余额充值