第一章 开始

具体内容点击查看

输入输出流

控制流:if/while/for

类简介

第二章 变量和基本类型

基本内置类型

具体内容点击查看
  1. unsigned int 可以缩写为 unsigned
  2. 当赋给无符号类型一个超出它表示范围的值时,结果为初始值对无符号类型表示数值总数取模后的余数。

变量

具体内容点击查看

变量的定义

初始化变量的方法
1
2
3
4
5
6
int a = 0;
int a(0);
int a{0};//c++11新特性
std::string words("初始化");
std::string words = "初始化";
std::String words{"初始化"};//c++新特性

变量的声明和定义的关系

extern关键字
1
2
3
4
5
//c++是一种静态类型语言,指其在编译阶段检查类型。检查类型的过程称为类型检查。

//声明一个变量而非定义它:使用extern关键字;并且不能显示初始化变量
//任何包含了显示初始化的声明就成了定义
extern int a;

标识符

标识符

标识符由字母、数字、下划线组成,且不能以数字开头。

不能连续出现两个下划线,不能以下划线紧连大写字母开头,定义在函数体外的标识符不能以下划线开头

名字的作用域

名字的作用域
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//作用域允许嵌套,且允许内层作用域中重新定义外层作用域已有的名字
#include <iostream>
using namespace std;

int reused = 23;//全局变量reused定义在全局作用域中
int main(){
std::cout<<reused<<std::endl;//23
int reused = 32;//重新定义了局部变量reused
std::cout<<reused<<std::endl;//32

std::cout<<::reused<<std::endl;//23
//使用作用域操作符显示地访问全局变量reused
//当作用域操作符::左侧为空时,请求获取全局作用域中作用域操作符右侧名字对应的变量
return 0;
}

复合类型

具体内容点击查看

引用

引用
1
2
3
4
5
6
7
8
//引用不是对象,只是对象的一个别名
int ival = 1024;
int &refval = ival;//refval指向ival(是ival的另一个名字),&为类型修饰符
//引用必须被初始化,即必须绑定一个对象,且引用的类型要与对象严格匹配

//引用是对象的别名,对引用的操作,实际上就是对那个对象的操作
refval = 2;//2赋给了ival
int li = refval//以引用为初始值,实际上是以引用所绑定的对象为初始值

指针

指针

与引用类似。指针也实现了对其他对象的间接访问
与引用不同。其一,指针是一个对象,可以进行赋值、拷贝,并可先后指向不同的对象。

其二,指针无须初始化。

1
2
3
4
//指针存放着它所指向对象的地址,通过取地址符&来获取地址
int ival = 42;
int *p = &ival;
//与引用类似,指针的类型也要与对象严格匹配
1
2
3
4
5
6
7
//使用解引用符*来访问指针所指的对象
int ival = 520;
int *p = &ival;
std::cout<<*p;//520
//对指针解引用会得到所指向的对象,所以对解引用的结果赋值实际上也就是给指针所指的对象赋值
*p = 21;
std::cout<<ival<<std::endl;//21

得到空指针的三种方法

1
2
3
4
5
6
int *p = 0;

int *p = nullptr;//c++11新特性

#include <cstdlib>
int *p = NULL;//用名为NULL的预处理变量给指针赋值

赋值永远改变的是等号左侧的对象

1
2
p = &ival;//pi的值变成了ival的地址,现在p指向了ival
*p = 0;//ival的值变成了0,指针p的值没有改变

void*指针是一种特殊的指针类型,能存放任意类型对象的地址

1
2
3
double obj = 3.14, *pd = &obj;
void *pv = &obj;//obj可以是任意类型的对象
pv = pd;//pv可以存放任意类型的指针

理解复合类型的声明

理解复合类型的声明
1
2
//变量的定义包括一个基本数据类型和一组声明符。虽然基本数据类型只有一个,但声明符的形式可以不用
int i = 1024, *p = &i, &r = i;

指向指针的指针

1
2
3
4
5
//声明符中的类型修饰符的个数没有限制,而指针也是对象,所以就有指向指针的指针
int ival = 1024;
int *pi = &ival;//指针pi指向一个int型的数
int **ppi = &pi;//指针ppi指向一个int型的指针
//通过*的个数来区分指针的级别,并可以通过多个解引用符*来访问

指向指针的引用

1
2
3
4
5
6
7
8
9
10
//从右往左读r的定义,来区分r的类型是什么
//从右往左离r最近的是&,所以r是一个引用
//通过声明符的其余部分来确定r引用的类型,*说明r引用的是一个指针,int说明r引用的是一个int指针
int i = 42;
int *p;
int *&r = p;//r是一个对指针p的引用

r = &i;//因为r引用了一个指针,所以给r赋值&i就是让p指向i
*r = 0;//因为r是指针p的引用,所以解引用r就是解引用p,解引用p得到p指向的对象i,所以最终将0赋给i
std::cout<<i;//0

const限定符

具体内容点击查看

const的特性仅仅在执行改变const对象内容的操作时才会发挥作用,所以const对象可以执行除了改变内容的所有操作;

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

const的引用

const 的引用
1
2
//”对const的引用“被称为“常量引用”
//const的对象同样可以绑定引用,与通常引用不同的是,对常量的引用不能被用来修改它所绑定的对象

初始化和对const的引用(常量引用)

1
2
3
4
5
6
//前面强调,引用的类型必须与引用对象的类型一致,但存在两个例外。
//第一种例外就是在初始化常量引用时允许用任意表达式作为初始值,只要表达式的结果能转换成引用的类型即可
int i = 42;
const int &r1 = i;//允许const int &绑定到一个普通的int对象上
const int &r2 = r1 * 2;//正确,因为r2是一个常量引用
int &r3 = r1 * 2;//错误,因为r3不是常量引用

对const的引用可以引用一个非const的对象

1
2
3
4
5
6
7
int i = 42;
int &r1 = i; //普通引用r1绑定i
disk int &r2 = i;//常量引用r2绑定i,但是,不允许通过r2修改i的值
r1 = 0;//正确,因为r1不是常量
r2 = 0;//错误,因为r2是一个常量引用

//当然,尽管不能通过r2修改i的值,但是仍然可以通过其他方式改变i的值,只要不通过常量引用r2修改即可。

指针和const

指针和const

指向常量的指针不能用于改变其所指对象的值,同样,要想存放常量对象的地址,只能使用指向常量的指针

1
2
3
4
disk double pi = 3.14;
double *ptr = &pi;//错误
disk double *cptr = &pi;//正确
*cptr = 42;//错误

但是一个指向常量的指针可以指向一个非常量对象,与引用类似,仅仅不能通过该指针改变对象的值,但可以通过其他途径改变

所谓指向常量的指针和引用,只不过是指针或引用“自以为是”罢了, 它们认为自己指向了常量,所以约束自己不去改变对象的值

指针是对象,所以也存在const指针

1
2
3
4
int errNumb = 0;
int *disk curErr = &errNumb;//curErr是一个常量指针,将一直指向errNumb
disk double pi = 3.14159;
disk double *disk pip = &pi;//pip是一个指向常量对象的常量指针

注意,常量指针不代表该指针所指向的对象是常量,所以可以通过常量指针来修改指向对象的值(非常量对象)

顶层const

顶层const

顶层const可以表示任意的对象是个常量

底层const表示指针(引用)所指(绑定)的对象是一个常量

1
2
3
4
5
6
int i = 0;
int *const p1 = &i;//不能改变p1的值,这是一个顶层const
const int ci = 42;//不能改变c1的值,这是一个顶层const
const int *p2 = &ci;//指针p2不是常量,这是一个底层const
const int *const p3 = p2;//左const为顶层const,右const为底层const
const int &r = ci;//用来声明引用的const都是底层const

当执行对象的拷贝操作时,顶层const几乎不受影响,但底层const却有诸多限制。

执行对象的拷贝操作时,拷入拷出的对象必须具有相同的底层const资格,或两个对象的数据类型能够转换。(非常量可以转换成常量)

1
2
3
4
5
6
7
8
i = ci;//正确
p2 = p3;//正确

int *p = p3;//错误,p3包含底层const的定义,但p没有
p2 = p3;//正确,p2和p3都是底层const
p2 = &i;//正确,int*能够转换成const int*
int &r = ci;//错误,普通的int&不能绑定到int常量上
const int &r2 = i;//正确,const int&可以绑定到一个普通int上

constexpr和常量表达式

constexpr和常量表达式
  • c++11新标准中,可以将变量声明为constexper类型以便编译器来验证变量的值是否是一个常量表达式
  • 声明为constexper的变量一定是一个常量,而且必须用常量表达式初始化。
  • 引用和指针也能定义成constexper,但初始值只能是nullptr、0、或是存储于某个固定地址中的对象
  • 在constexper声明中如果定义了一个指针,限定符constexper仅对指针有效,与指针所指的对象无关
1
2
disk int *p = nullptr;//p是一个指向整型常量的指针
diskexper int *q = nullptr;//q是一个指向整数的常量指针

处理类型

具体内容点击查看

类型别名

类型别名

两种方法定义类型别名。

传统的方法是使用关键字typedef:

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

c++新标准规定了一个新方法:使用别名声明来定义类型的别名

1
2
using SI = Sales_item;//SI是Sales_item的同义词
//使用关键字using后接别名和等号

特别注意,当类型别名指代的是复合类型或常量时别名所指代的数据类型是什么

1
2
3
4
//typedef char *pstring;
using pstring = char*;

disk pstring cstr = 0;//注意:cstr是指向char的常量指针,而不是指向一个char型常量的指针

由于别名声明的是一个指向char的常量指针,声明语句中用到pstring时,基本数据类型是指针!

auto类型说明符

auto类型说明符
  • c++新标准中引入了auto型说明符,作用是让编译器来判断表达式所属的类型

  • auto也可以在一条语句中声明多个变量,前提是所有变量的基本数据类型一致

1
auto item = val1 + vall2;//item初始化为val1和val2相加的结果
  • 当auto类型用来定义复合类型时,auto通常会忽略顶层const,并保留底层const
1
2
3
4
disk int ci = 0, &cr = ci;
auto b = ci;//b是int型
auto c = cr;//c是int型,因为cr是ci的引用,ci是int型
auto d = &ci;//d是指向整型常量的指针

decltype 类型指示符

decltype类型指示符
  • c++11新标准引入第二种类型说明符: decltype

  • decltype 让编译器分析表达式并得到它的类型,但也仅仅是得到类型,而不实际计算表达式的值

1
decltype (f()) sum = x;//sum的类型为函数f()的返回类型
  • 与auto不同,decltype返回的类型包括了顶层const和底层const
decltype和引用
  • 特别的,引用一向作为对象的同义词出现,除了在decltype这里被返回时返回的是引用类型

  • decltype与auto的另一重要区别是:decltype的结果类型与表达式的形式密切相关

  • 如果decltype()中的变量名加上了括号,那decltype((variable))的结果将永远是引用类型,而不加括号decltype(variable)的结果只有当variable本身是引用时才是引用
1
2
3
int i = 0;
decltype((i)) d;//错误,因为有双括号,所以d为引用类型,必须初始化
decltype(i) e;//正确,e是一个int型

自定义数据结构

具体内容点击查看

详情请看 c++primer p90~93

预处理器

预处理器
1
2
3
4
5
6
7
8
9
10
11
预处理器是用来确保头文件多次包含仍能安全工作的常用技术

* #include

当预处理器看到#include 标记时就会有指定的头文件的内容代替#include

* 头文件保护符:依赖于预处理变量

预处理变量有两种状态:已定义和未定义

#define 指令把一个名字设定为预处理变量,另外两个指令分别检查某个指定的预处理变量是否已经定义:#ifdef 当且仅当变量已定义时为真,#ifndef 当且仅当变量未定义时为真。一旦检查结果为真,则执行后续操作直至遇到#endif 指令为止。如果头文件被多次包含,则#ifndef 的检查结果为假,编译器将忽略#ifndef 到#endif 之间的部分。

c++primer 第三章请点击下方的站内链接