主页
文章
分类
系列
标签
简历
【C++ Primer(edition 5) 01】变量和基本类型
发布于: 2021-9-4   更新于: 2021-9-4   收录于: Cpp
文章字数: 662   阅读时间: 4 分钟   阅读量:

一、基本内置类型

基本算数类型

类型 含义 最小尺寸
bool 布尔类型 8bits
char 字符 8bits
wchar_t 宽字符 16bits
char16_t Unicode字符 16bits
char32_t Unicode字符 32bits
short 短整型 16bits
int 整型 16bits (在32位机器中是32bits)
long 长整型 32bits
long long 长整型 64bits (是在C++11中新定义的)
float 单精度浮点数 6位有效数字
double 双精度浮点数 10位有效数字
long double 扩展精度浮点数 10位有效数字

如何选择类型

  • 1.当明确知晓数值不可能是负数时,选用无符号类型;
  • 2.使用int执行整数运算。一般long的大小和int一样,而short常常显得太小。除非超过了int的范围,选择long long
  • 3.算术表达式中不要使用charbool
  • 4.浮点运算选用double

二、常量

在C++中,常量类似于变量,只是不能修改。与变量一样,常量也占用内存空间,并使用名称标识为其预留的空间的地址,但不能覆盖该空间的内容。在C++中,常量可以是: •字面常量; •使用关键字const声明的常量; •使用关键字constexpr声明的常量表达式(C++11新增的); •使用关键字enum声明的枚举常量; •使用#define定义的常量(已摒弃,不推荐)。

1、使用constexpr声明常量

在C++11之前,C++就支持常量表达式的概念,只是没有关键字constexpr,在程序清单3.5中, 22.0/7是一个常量表达式,C++11之前的编译器也支持它。然而,C++11之前的编译器不允许定义在编译阶段计算的函数。 在C++11中,可以编写下面这样的代码:

1
constexpr double GetPi() {return 22.0 / 7;}

还可将GetPi与另一个常量一起使用,如下所示:

1
constexpr double TwicePi() {return 2 * GetPi();}

乍一看,const和constexpr之间的差别很小,但从编译器和应用程序的角度看,关键字Constexpr 提供了优化应用程序的可能性。对于第二条语句,如果使用const,将在运行阶段执行计算,但使用遵 守C++11的编译器时,将在编译阶段计算该表达式的值,这提高了应用程序的运行速度。

2、字面值常量

  • 一个形如42的值被称作字面值常量(literal)。
    • 整型和浮点型字面值。
    • 字符和字符串字面值。
      • 使用空格连接,继承自C。
      • 字符字面值:单引号, 'a'
      • 字符串字面值:双引号, "Hello World""
      • 分多行书写字符串。
1
2
std:cout<<"wow, a really, really long string"
		"literal that spans two lines" <<std::endl;
  • 转义序列。\n\t等。
  • 布尔字面值。truefalse
  • 指针字面值。nullptr

字符串型实际上时常量字符构成的数组,结尾处以'\0'结束,所以字符串类型实际上长度比内容多1。

3、使用const将变量声明为常量

[[变量和基本类型#五、const限定符]]

4、枚举常量

在有些情况下,变量只能有一组特定的取值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
enum RainbowColors
{
	Violet = 0 , //此后每个值都递增1,默认0开始
	Indigo,
	Blue,
	Yellow,
	Orange,
	Red
}

RainbowColors MyWorldColor = Blue;

5、使用#define定义常量 【摒弃】

这是一个预处理器宏,让预处理器将随后出现的所有Pi都替换为3.14286。预处理器将进行文本替换,而不是智能替换。编译器既不知道也不关心常量的类型

1
#define Pi 3.14

三、变量

1、什么是变量

变量提供一个具名的,可供程序操作的存储空间 变量类型向编译器指出了变量可存储的数据的性质,编译器将为变量预留必要的空间。变量名由程序员选择,它替代了变量值在内存中的存储地址,但更友好。除非给变量赋初值,否则无法确保相应内存单元的内容是什么,这对程序可能不利。因此,初始化虽然是可选的,但对变量初始化通常是 一个不错的编程习惯。程序清单3.1将用户提供的两个数字相乘,演示了如何在程序中声明、初始化。

变量名 是内存地址的别名,方便存取 类型 是告诉编译器可以存什么样的数据,需要多大的内存空间 初始化 是可选的,不然无法确保内存中的数据是啥 变量提供一个具名的、可供程序操作的存储空间。 C++变量对象一般可以互换使用。

2、变量定义(define)

定义形式:类型说明符(type specifier) + 一个或多个变量名组成的列表。用于初始化变量的值可以是任意复杂的[[表达式#^0c892c|表达式]]

1
int sum = 0, value, units_sold = 0;

3、初始化

初始化(initialize):对象在创建时获得了一个特定的值。 初始化不是赋值! 初始化 = 创建变量 + 赋予初始值 赋值 = 擦除对象的当前值 + 用新值代替

3.1 直接初始化

3.2 列表初始化(C++11 全面支持)

使用花括号{}

1
2
3
4
int units_sold = 0;
int units_sold = {0};
int units_sold{0};
int units_sold(0);

当用于内置类型的变量时,这种初始化形式有一个重要特点:如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错

1
2
3
long double Id = 3.1415926536;
int a{Id}, b = {Id}; // 错误 :转换未执行,因为存在丢失信息的危险
int c(ld), d = Id;   // 正确:转换执行,且确实丢失了部分值

3.3 默认初始化

定义时没有指定初始值会被默认初始化,默认值到底是什么由变量类型决定

在函数体内部的内置类型变量将不会被初始化, 函数体外的内置类型初始化为0,试图使用未定义的变量将发生错误。建议初始化每一个内置类型的变量。

3.4 值初始化(类)

通常情况下,可以只提供Vector对象容纳的元素数量而不用略去初始值。此时库会创建一个值初始化的(value-initialized)元素初值,并把它赋给容器中的所有元素。这个 初值由vector对象中元素的类型决定。 限制:类中的元素必须支持默认初始化

4、变量的声明(declaration) vs 定义(define)

  • 为了支持分离式编译,C++将声明和定义区分开。声明使得名字为程序所知。定义负责创建与名字关联的实体。
  • extern:只是说明变量定义在其他地方。
  • 只声明而不定义: 在变量名前添加关键字 extern,如extern int i;。但如果包含了初始值,就变成了定义:extern double pi = 3.14;
  • 变量只能被定义一次,但是可以多次声明。定义只出现在一个文件中,其他文件使用该变量时需要对其声明。
  • 名字的作用域(namescope){}
    • 第一次使用变量时再定义它
    • 嵌套的作用域
      • 同时存在全局和局部变量时,已定义局部变量的作用域中可用::reused显式访问全局变量reused。
      • 但是用到全局变量时,尽量不适用重名的局部变量。

5、变量命名规范

  1. 需体现实际意义
  2. 变量名用小写字母
  3. 自定义类名用大写字母开头:Sales_item
  4. 标识符由多个单词组成,中间须有明确区分:student_loan或studentLoan,不要用studentloan。

6、左值和右值

  • 左值(l-value)可以出现在赋值语句的左边或者右边,比如变量;
  • 右值(r-value)只能出现在赋值语句的右边,比如常量。

7、变量的作用域

C++作用域

四、复合类型

复合类型

五、const限定符

  • 动机:希望定义一些不能被改变值的变量。

1、初始化和const

  • const对象必须初始化,且不能被改变
  • 编译器将在编译过程中把用到const 变量的地方都替换成对应的值。
  • const变量默认不能被其他文件访问,非要访问,必须在指定const定义之前加extern。某些时候有这样种const变量,它的初始值不是个常量表达式,但又确实有必要在文件间共享。也就是说,只在一个文件中定义const,而在其他多个文件中声明并使用它。解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:
1
2
3
4
// file_l.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fen();
// file_l.h 头丈件
extern const int bufSize; // 与	.cc 中定义的 bufSize 是同一个

2、const的引用

[[复合类型#^d7c880|常量引用的特殊性]]

  • reference to const(对常量的引用):指向const对象的引用,如 const int ival=1; const int &refVal = ival;,可以读取但不能修改refVal
  • 临时量(temporary)对象:当编译器需要一个空间来暂存表达式的求值结果时,临时创建的一个未命名的对象。
  • 对临时量的引用是非法行为。

3、指针和const

[[复合类型#^413a8b|常量指针的特殊性]]

  • pointer to const(指向常量的指针,简称常量指针):不能用于改变其所指对象的值, 如 const double pi = 3.14; const double *cptr = &pi;
  • const pointer:指针本身是常量(简称指针常量),也就是说指针固定指向该对象,(存放在指针中的地址不变,地址所对应的那个对象值可以修改)如 int i = 0; int *const ptr = &i;

4、顶层const与底层

  • 顶层const:表示变量本身是个常量,对任何数据类型适用,如算数类型(int、double),类,指针。
  • 底层const:表示指向的变量是个常量,用于指针和引用

六、处理类型

1、类型别名

  • 传统别名:使用typedef来定义类型的同义词。
1
2
typedef double wages;
typedef wages base,*p; //base = wages = double , p = wages * = double *

这里不仅有基本类型还包含声明符,需要理解[[复合类型#四、理解复合类型的声明|复合类型的声明]]

  • 新标准别名:别名声明(alias declaration): using SI = Sales_item;(C++11)

指针、常量和类型别名

1
2
3
4
5
6
7
8
9
// 对于复合类型(指针等)不能代回原式来进行理解
typedef char *pstring;  // pstring是char*的别名
const pstring cstr = 0; // 指向char的常量指针
const pstring *ps; //ps是一个指针,他的对象是指向char的常量指针
// 如改写为const char *cstr = 0;不正确,为指向const char的指针

// 辅助理解(可代回后加括号)
// const pstring cstr = 0;代回后const (char *) cstr = 0;
// const char *cstr = 0;即为(const char *) cstr = 0;

2、auto类型说明符 c++11

  • auto类型说明符:让编译器自动推断类型
  • 一条声明语句只能有一个数据类型,所以一个auto声明多个变量时只能相同的变量类型(包括复杂类型&和*)。auto sz = 0, pi =3.14//错误
  • 如何初始化的对象是引用类型,会根据引用变量的引用对象的值来推测,而非引用变量的类型,int i = 0, &r = i; auto a = r; 推断a的类型是int
  • 会忽略顶层constconst int ci = 1; const auto f = ci;推断类型是int,如果希望是顶层const需要自己加const;但是如果左值是一个引用变量,那么初始值中的顶层常量属性会被保存
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
int i = 0, &r = i;
auto a = r;   //a 是整型
const int ci = i, &cr = ci;
auto b = ci;  //b 是整型
auto c = cr;  //c 是一个整型
auto d = &i;  //d 是一个整型指针
auto e = &ci; //e 是一个指向整数常量的指针(对常量取地址是一种底层const)
const auto f = ci; //ci的推演类型是int,f是cosnt int

auto &g = ci; //g是一个整型常量引用,绑定到ci
auto &h = 42; //错误:不能为非常量引用绑定到字面值
const auto &j = 42 //正确,这里是因为常量引用的特殊性,可以参考复合类型的笔记

3、decltype类型指示符

有时会遇到这种情况: 向往从表达式的类型推断出要定义的变量的类型,但是不想用该表达式的值初始化变量(想用类型,不想用值)。 为了满足这一要求,C++1I新标准引入了第二种类型说明符decltype decltype:选择并返回操作数的数据类型,从表达式的类型推断出要定义的变量的类型。

3.1 与auto的不同

decltype处理顶层const和引用的方式与auto有些许不同。 其一,如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内):

1
2
3
4
const int ci =0, &cj =ci;
decltype(ci) x = 0 ;   //x的类型是const int 
decltype(cj) y = x ;   //y的类型是const int &
decltype(cj) z;        //错误,z是const int & ,需要初始化

其二,decltype的结果类型与表达式形式密切相关,如果对变量加括号(一层或者多层),因为变量是一种可以作为赋值语句左值的特殊表达式,编译器会因此将其认为是一个表达式

1
2
decltype((i)) d; //错误,d是int&,必须初始化
decltype(i) e; //正确,e是一个未初始化的int

3.2 decltype和引用

  • 如果decltype使用的表达式不是一个变量,则decltype返回表达式结果对应的 类型。
1
2
3
4
5
6
decltype(f()) sum = x; // 推断sum的类型是函数f的返回类型。

int i = 42, *p = &i, &r = i ;
decltype(r + 0) b; //正确,加法的结果是int,因此b是一个未初始化的int
decltype(*p) c; //错误:c是int& , 必须初始化
decltype(&i) d; //d是 int **类型
  • 因为r是一个引用,因此decltype (r)的结果是引用类型。如果想让结果类型是r所指 的类型,可以把r作为表达式的一部分,如r+0,显然这个表达式的结果将是一个具体值而非一个引用。
  • 表达式的内容是解引用操作,则decltype将得到引用类型
  • 赋值是会产生引用的一类典型表达式,引用的类型就是左值的类型。也就是说,如果 i 是 int,则表达式 i=x 的类型是 int&。
  • 取地址运算符生成右值,所以decltype(&p)的结果是int **