【C++ Primer 5th】 第二章.变量与基本类型 笔记 (数据类型 | 变量|指针|&|const|auto|typedef|decltype|头文件|预处理)

本文参考书籍C++Primer 5th,这个专栏主要分享我的学习笔记与总结,以及课后题答案。
在这里插入图片描述

前言:经过第一章的教训,我准备把笔记和练习题分开 ,不然特别的乱。

  • 内容 :
    数据类型是程序的基础:它告诉我们数据的意义以及我们能在数据上执行的操作,并且数据类型决定了程序中数据和操作的意义。
    数据类型存在的意义:给变量分配合适的内存空间

一、基本内置类型

C++定义了一套包括算术类型(arithmetic type)和空类型(void)在内的基本数据类型。其中算术类型包含了字符、整型数、布尔值和浮点数。空类型不对应具体的值。

复合类型

前置知识:存储单位的bit 和 Byte
bit(比特)
电脑是以二进制存储以及发送接收数据的。二进制的一位,就叫做 1 bit。也就是说 bit 的含义就是二进制数中的一个数位,即 “0” 或者 “1”。
Byte(字节)
英文字符通常是一个字节,也就是 1Byte (B)。1 Byte = 8 bit.


C++:算术类型

类型含义最小尺寸(bit)
bool布尔类型未定义
char字符8 位
wchar_t宽字符16 位
charl6_tUnicode 字符16 位
char32_tUnicode 字符32 位
short短整型16 位
int整型16 位
long长整型32 位
long long (C11)长整型64 位
float单精度浮点数7 位有效数字 (4字节)
double双精度浮点数15~16 位有效数字(8字节)
long double扩展精度浮点数10 位有效数字

编译器浮点数类型默认显示小数点后6位 (这个数是小于1的情况下)
另外除了与long随操作系统子长变化而变化外,其他的都固定不变(32位和64相比)

⽆符号类型

int、short、long和long long都是带符号的,前面加上unsigned就可以得到无符 号类型,例如unsigned long
unsigned int可以缩写成unsigned
char比较特殊,类型分为三种:char、signed char、unsigned char
charsigned charunsigned char的其中一种(编译器决定)
8 bytechar 范围:-128~127 ,unsigned char 范围:0 ~ 255

建议:如何选择类型
①当明确知晓数值不可能为负时,选用无符号类型。
②使用int执行整数运算。在实际应用中,short常常显得太小而long一般和int有一样的尺寸。如果你的数值超过了int的表示范围,选用long long。
③在算术表达式(加减乘除)中不要使用char或bool,只有在存放字符或布尔值时才使用它们。因为char在不同编译器是不同的
④ 执行浮点数运算选用double,这是因为float通常精度不够而且\双精度运算甚至比单精度还快。long double提供的精度在一般情况下是没有必要的,况且它带来的运行时消耗也不容忽视。

类型转化

bool b=42; //b为真(1)  bool的值非0即1  不宜这算术表达式用布尔值
int i=1; //i == 1 
int i=3.14;// i == 3 精度丢失
double pi=i;//假设char占8比特,c的值为255
unsigned char c=-1;//假设char占8比特,c的值为255
signed char c2=256;//假设char占8比特,c2的值未定义,可能是垃圾值,也可能崩溃

注意:char在一些机器上是有符号的,而在另一些机器上是无符号的
建议:避免无法预知和依赖于实现环境的行为(不可移植),如果移植到其他机器,就会保错

含⽆符号类型的表达式

不要在for循环中使用无符号,当u等于0时,--u的结果将会是4294967295
在这里插入图片描述
解决:
在这里插入图片描述

切勿混⽤带符号类型和⽆符号类型

unsigned u=10;
int i=-42; 
std::cout << i+i << std::endl;//输出-84 
std::cout << u+i << std::endl;//如果int占32位,输出4294967264
//2的32次方为4294967296 全是1表示的是-1,因为-1+1=0

字面值常量

比如一个数字42的值被称为字面值常量,字面值常量等等形式和指绝对了它的数据类型。

整形和浮点型字⾯值
• 可以将整形写成十进制、八进制或十六进制
在这里插入图片描述
• 浮点型字面值是一个double类型的值,表现为一个小数或科学计数法的指数形式 :
3.14159 3.14169E0

• 字符和字符串字面值

'a'//字符字面值 "Hello World"//字符串字面值 /0结尾

• 转义序列,C++定义的转义序列包括:
需要带\开头才能打印
在这里插入图片描述

• 泛化的转义序列,其形式是\x后紧跟1个或多个十六进制的数字,或、后面紧跟1/2 或3个八进制的数字。假设使用的是Latin-1字符集,以下是一些示例:
在这里插入图片描述
• 指定字面值的类型
在这里插入图片描述

L'a'//宽字符型字面值,类型是wchar_t
u8"hi!"  // utf-8 字符串字面值(utf-8 用8 位编码一个 Unicode 字符)
42ULL // 无符号整型字面值,类型是 unsigned long long 
1E - 3F//单精度浮点型字面值,类型是 float 
3.14159L//扩展精度浮点型字面值,类型是 long double
//尽量用大写 避免把l看出1

• 指针 :nullptr
• 布尔:false true

二、变量

概念

• 变量提供一个具有名称的、可供程序操作的存储空间。
• 变量都具有数据类型 • 通过数据类型可以决定变量所需要的内存空间、布局方式、以及能够表示值的范围。

	int sum=0,value,//sum、value和unit_sold都是int
    units_sold=0;//sum和units_sold初值为0 
    Sales_item item;//item的类型是Sales_item
    //string 是一种库类型,表示一个可变长的字符序列 
    std::string book("0-2-1-78345-X");
   //book通过一个string字面值初始化

对于C++程序员来说,变量(variable)和对象(object)一般是可以互换的。

初始化

列表初始化

作为C++新标准的一部分,使用花括号来初始化变量得到了全面应用 , 如果我们使用列表初始化且初始值存在丢失信息的风险,则编译器将报错.

int units_sold = 0;//四条语句等价
int units_sold = {0};//列表初始化
int units_sold{0};//列表初始化 
int units_sold(0);
long double ld = 3.1415926536; 
int a{ld},b={ld};//错误:转换未执行,因为存在丢失信息的风险 
int c(ld),d=ld;//虽然正确:转换执行,但会丢失精度(部分值)

默认初始化
• 如果定义变量没有定义初始值,则变量被赋予默认值。
• 默认值是由变量类型决定的,同时定义变量的位置也会有影响。
• 内置类型:由定义的位置决定,函数体之外初始化为0
• 每个类各种决定其初始化对象的方式

std:::string empty;//empty非现实地初始化一个空串
Sales_item item;//被默认初始化的Sales_item对象

未初始化的变量含有一个不确定的值,将带来无法预计的后果,应该避免

变量的定义与声明

C++是一种静态类型语言,其含义是在编译阶段检查类型。这就要求我们在使用 某个变量之前必须先声明。如果想声明一个变量而非定义它,就在变量名前添加extern关键字,而且不要显示的初始化。

extern double pi = 3.14;//这是定义,不能放在函数体内部,只能放函数外
int main() {
 extern int i;//声明i而非定义i 
 int j;//声明并定义j
}

变量能且只能定义一次,但可以被声明多次。如果要在多个文件使用同一个变量,就必须将声明和定义分离,定义的变量必须且只能出现在一个文件中

标识符

就是给变量起名,C++标识符(identifier)由字母、数字和下划线组成,其中必须以字母或下划线开头。标识符的长度没有限制,但对大小写敏感。 还有,关键字不能作为标识符。
在这里插入图片描述

作用域

同一个名字如果出现在程序的不同位置,也可能指向不同的实体。 C++中大多数作用域都以花括号分隔。名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。

#include <iostream> 
int main(){ //全局作用域
	int sum = 0;//sum存在全局作用域  它在该区域都能起作用
	for(int val = 1; val<=10; ++val) {//块作用域 val 只能存在于这个作用域起作用
	sum+=val;//等价于sum=sum+val
	}
	std::cout<<"sum of 1 to 10 inclusive is "<<sum<<std::endl;
	return 0;
}

如果函数有可能用到某全局变量,则不宜再定义一个同名的局部变量。C++特性:全局变量与局部变量同时存在,优先使用局部变量

#include <iostream>
// 该程序是一个不好的示例,仅用于展示作用域 int reused = 42; // reused 拥有全局作用域
int main() {
	int unique = 0; // unique 拥有块作用域
	// output #1: 42 0 
	std::cout << reused << " " << unique << std::endl; int reused = 0; // 同名的新建局部变量,覆盖了全局变量
	// output #2: 0 0 
	std::cout << reused << " " << unique << std::endl;
	// output #3: 显式地访问全局变量,打印 42 0 
	std::cout << ::reused << " " << unique << std::endl; 
	return 0;
}

三、复合类型

复合类型(compound type) 是指基于其他类型定义的类型。

引⽤(reference)

为对象起的另一个名字定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用,引用本身并不是对象,所以不能定义引用的引用(&&),引用必须初始化对象。
C++11中新增了“右值引用”;当我们使用术 语“引用”时,一般指的是“左值引用”。引用不是对象,它只是为一个已经存在的对象所起的另一个名字

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

refVal = 2;//把2给refVal指向的对象,此处即是赋给了ival 
int li = refVal;//等同于li=ival

//正确:refVal13绑定到了那个与refVal绑定的对象上,即绑定了
ival int &refVal3 = refVal;
//利用与refVal绑定的对象的值初始化变量i 
int i = refVal;//正确:i被初始化为ival的值

指针(pointer)

对地址的封装,本身就是一个对象
引用不是对象,不存在地址,所以不能定义指向引用的指针。
定义指针类型的方法是将声明符写成*p的形式 ,建议类型和*写一起
• 如果一条语句中定义了几个指针变量,每个变量前面都必须加上*符号
• 和其他内置类型一样,在块作用域内定义指针如果没有初始化,将
拥有一个不确定的值。

int *ip1, *ip2;//ip1和ip2都是指向int型对象的指针 
double* dp2;//推荐写法  分行写 并且 类型和*写一起
double* dp1;

•可以使用取地址符(操作符&)获取指针所封装的地址:

int ival = 42; 
int *p = &ival;//p是指向ival的指针 
double *dp = &ival;//错误:类型不匹配

•可以使用解引⽤符(操作符*)利用指针访问对象:

int ival = 42; 
int *p = &ival;//p是指向ival的指针
std::cout<<*p ;//输出42 
*p=0;  //等价与 ival = 0 
std::cout<<*p; //输出0

在声明中,&和*用于 组成复合类型;在表 达式中,他们是运算 符。含义截然不同。

在这里插入图片描述

指针值

如果指针指向了一个其他类型的对象,对该对象的操作将发生错误。

  • 指针的值(即地址)应属下列 4种状态之一:
    1 .指向一个对象。
    2 .指向紧邻对象所占空间的下一个位置。
    3 .空指针,意味着指针没有指向任何对象。
    4 .无效指针,也就是上述情况之外的其他值。
    建议:初始化所有指针,如果实在不知道指向哪,就给它赋值为空指针

空指针

不指向任何对象 在使用一个指针之前,可以首先检查它是否为空.

int *p1 = nullptr; //C++11  推荐写法
int *p2 = 0;
int *p3 = NULL; //需要#include cstdlib
int zero = 0; 
p1 = zero; //错误:类型不匹配

void *指针

纯粹的地址封装,与类型无关。可以用于存放任意对象的地址。

double obj = 3.14, *pd = &obj; 
void *pv = &obj;
pv = pd;

指向指针的指针

通过*的个数可以区分指针的级别。 (套娃行为)

int ival = 1024; 
int *pi = &ival; 
int **ppi = &pi;//ppi指向一个int型的指针

指向指针的引用

指针是对象,可以定义引用。 引用不是对象,所以只存在指向指针的引用

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

建议:对于复杂的指针or引用,从右向左阅读更容易看懂!

四、const 限定符

const限定符:把变量定义成⼀个常量,使用const对变量的类型加以限定,变量的值不能被改变。 防止出现风险

初始化

const对象必须初始化(其他时候不能出现在等号左边)。

const int bufSize = 512; //输入缓冲区大小
bufSize = 512; //错误:试图向const对象写值

const int i = get_size(); //正确:运行时初始化 
const int j = 42; //正确:编译时初始化 
const int k; //错误:k是一个未经初始化的常量

const int bb=0; 
void * a= bb;//在编译的时候,会把bb编程常量

默认状态下,const对象仅在文件内有效 如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字

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

const的引⽤:常量引用

要求 引用类型必须与其所引用的对象的类型一致

const int ci = 1024; 
const int &r1 = ci; //正确:引用及其绑定的对象都是常量 (常量引用)
r1 = 42;//错误,相当于c1=42,试图修改常量 
int &r2 = ci;//错误:ci是常量,存在通过r2改变ci(const)的风险

int i = 42; 
const int &r1 = i; //正确:i依然可以通过其他途径修改 
const int &r2 = 42; 
const int &r3 = r1*2; 
int &r4 = r1*2; //错误:不能通过一个非常量的引用指向一个常量

如果类型不同则会出现 临时量对象 (临时量)

double dval = 3.14;
const int &ri = dval; //这个语句实际上会运行为 以下俩条语句组成
const int tmep = dval;//把double 转化为 int 类型常量
const int &ri = temp;

以上这种行为 C++中称为非法行为

指针和const

指向常量的指针:把const 放在 * 之前,不变的数指针的值,而不是指向的那个值

const double pi = 3.14;
double *ptr = &pi; //错误:存在通过ptr指针修改pi的风险
const double * cptr = &pi;  //相当于限制了解引用操作
*cptr = 42; //错误
double dval = 3.14; 
cptr = &dval; //正确:但不能通过cptr修改dval的值  改地址不影响

const指针:指针是对象,也可以限定为常量(必须初始化)
• 把*放在const之前,说明指针是一个常量
• 不变的是指针本身的值,而非指向的那个值

int errNumb = 0; 
int *const curErr = &errNumb; 
const double pi = 3.14159; 
const double *const pip = &pi;//指向常量的常量指针
*pip = 2.71;//错误: 试图修改常量pi
if(*curErr){ 
	errorHandler();
	*curErr = 0; //正确:试图修改变量errNumb
}

顶层const & 底层const

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

int i = 0; int *const p1 = &i; //顶层 
const int ci = 42; //顶层 
const int *p2 = &ci;//底层 
const int *const p3 = p2;//(左:底层 ), (右:顶层)

i = ci;//正确 
p2 = p3;//正确

int *p = p3;//错误:存在通过*p修改*p3(const)的风险 
p2 = &i; //正确:只是不能通过p2修改i而已 
int &r = ci; //错误:存在通过r修改ci(const)的风险 
const int &r2 = i; //正确:只是不能通过r2修改i而已

constexpr和常量表达式

• 常量表达式(const expression)是指:值不会改变并且在编译过程就能得到计算结果的表达式。自定义类型(例如:Sales_item)、IO库、 string等类型不能被定义为constexpr。
• C++11标准规定,允许将变量声明为constexpr类型,以便由编译器来验证变量的值是否是一个常量表达式。
• 一定是一个常量
• 必须用常量表达式初始化

如果你认为变量是一个常量表达式,那就把它声明成constexpr.

constexpr int mf = 20; 
constexpr int limit = mf +1; 
constexpr int sz = size(); //只有当size是一个constexpr函数时才正确

指针和constexpr
• 限定符仅对指针有效,对指针所指的对象无关

// i j 必须定义在函数体外
int j = 0; 
constexpr int i = 42;
int main() {
    constexpr int *np = nullptr; //常量指针 
    constexpr const int *p = &i; //p是常量指针,指向常量 
    constexpr int *p1 = &j; //p1是常量指针,指向变量j
    return 0;
}   

五、处理类型

随着程序越来越复杂,程序中的变量也越来越复杂。
• 拼写变得越来越困难。 -> typedef
• 搞不清楚变量到底需要什么类型。 ->auto

typedef

类型别名:提⾼可读性,

typedef double wages; 
typedef wages base, *p; //base是double的同义词,p是double *的同义词
using SI = Sales_item; //C++11,别名声明
wages hourly, weekly; //等价于double hourly, weekly
SI item; //等价于Sales_item item

对于指针这样的复合类型,类型别名的使用可能会产生意想不到的结果

typedef char *pstring;   //pstring 是 char* 的同义词
const pstring cstr = 0; //指向char的常量指针 
const pstring *ps; //ps是指针变量,它的对象是指向char的常量指针
const char *cstr = 0; //是对const pstring cstr =0 的错误理解

auto

atuo类型说明符:C++11,让编译器通过初始值推断变量的类型

auto item = val1 + val2;
auto i =0, *p = &i; //正确 
auto sz = 0, pi = 3.14; //错误:auto已经被推断为int型,却需要被推断为double

auto会忽略顶层const

int i = 0, &r = i; 
auto a = r; //a是int型

const int ci = i, &cr = ci; 
auto b = ci; //b是int型,ci的顶层const被忽略 
auto c = cr; //c是int型,ci的顶层const被忽略 
auto d = &i; //d是整形指针,整数的地址就是指向整形的指针 
auto e = &ci; //e是指向整数常量的指针(底层const没有被忽略)

const auto f = ci; //auto的推演类型为 int,f是const int 
auto &g = ci; //g是一个整形常量引用,绑定到ci,(底层const没有被忽略)
auto &h = 42; //错误:不能为非常量引用绑定字面值 
const auto &j =42; //正确:可以为常量引用绑定字面值

auto k = ci, &l = i;  //k 是整数 , l是整型引用
auto &m = ci, *p = &ci; //m 是对整型常量的引用  p是指向整型常量的指针
auto &n = i, *p2 = &ci; //errot : i是int   &ci 是const int 类型不同

decltype

decltype类型说明符:选择并返回操作数的数据类型 只要数据类型,不要其值

decltype(f()) sum = x;// sum的类型就是函数f返回的类型
const int ci = 0, &cj = ci; 
decltype(ci) x = 0; // x的类型是const int 
decltype(cj) y = x; // y的类型是const int & 
decltype(cj) z; //错误:z是一个引用,必须初始化

*decltype和引用
如果是解引用操作 *p 那么decltype 会得到 引用类型
decltype((variable)):双层括号,结果永远是引用。 多一层()相当于 加上&

int i = 42, *p = &i, &r = i; 
decltype(r+0) b; //正确:b为int型
//注意:下面的*不是出现在声明中,而是表达式中的解引用运算符 
decltype(*p) c; //错误:解引用表达式,c的类型为引用int &,需要初始化
//变量如果是加上括号, decltype的结果将是引用 
decltype( (i) ) d; //错误:d是int&类型,必须初始化 
decltype( ( (i) ) ) d1 = i; //正确:d1是int&类型,绑定为了i 
decltype( i ) e; //正确:e是一个(未初始化的)int

如果加上多重括号,只会有一个& , 不存在引用的引用&&/

六、自定义数据结构

类型

⾃定义数据结构:⼀组数据以及相关操作的集合
类定义:类定义可以使⽤关键字class或struct
• 二者默认的继承访问权限不同
structpublic的,classprivate
•结尾分号不可少!!

struct Sales_data{ 
	std::string bookNo; 
	unsigned units_sold = 0; //C++ 11 
	double revenue = 0.0;
};//类定义的最后需要加上分号

建议定义类型对象写法:
Sales_data accum,trans,*salesptr;

类的使用

在这里插入图片描述

头文件 “xx.h”

类一般都不定义这函数体内,而是定义在头文件内,如果要在不同的文件中使用同一个类,类的定义就必须保存一致。头文件一旦改变相关的源文件就必须重新编译以获取更新过的声明。
编写⾃⼰的头文件:类通常定义在头文件中

//预处理器(preprocessor), 在编译之前执行的代码。
// 这里预处理器的功能是头文件保护符。
//其作用是防止重复包含的发生,只会调用一次"string.h"
#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

#include 实际也是预处理,当预处理器看到#include 标记时就会用指定的头文件的内容代替#include

预处理变量无视C++语言中关于作用域的规则。
整个程序预处理变量包括头文件保护符必须唯一,通常是基于头文件中类的名字命名的,一般预处理变量的名字全部大写

在这里插入图片描述

.

🌹感谢阅读🌹

评论 23
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

极客小腾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值