最近接到了一个Windows客户端开发,需要用到C++,由于大学嵌入式学习的时候用到了这种东西,虽然没忘记吧,但是还是打算用一周的时间复习一下,下面是我的复习笔记,当然了,也是基于尚硅谷和黑马的笔记以及菜鸟教程(吐槽一下:黑马和尚硅谷讲的并不好),之所以没有看视频,是因为作为java程序员,基本的语法还是相通的。
文章目录
1.Hello Word介绍
//引入"iostream"库
#include <iostream>
/*
*使用命名空间,如果不使用命令空间,“cout << "Hello Word" << endl”,将要写为“std::cout <<
* "Hello Word" << endl”
*/
using namespace std;
//主函数
int main() {
//输出Hello Word
cout << "Hello Word!" << endl;
//从控制台输如一个方法,主要是想要程序停滞
cin.get();
//返回值:最后一行语句就是返回一个值,大多数系统中,main的返回值是用来指示状态的,0表示成功,非0表示失败,当让
return 0;
}
1.1命名空间
在C++应用程序中,你可以写一个xyz的函数,在另外一个可用的库也存在一个相同的函数xyz,这样,编译器无法判断您所使用的是哪一个xyz()函数,因此引入了
命名空间
这个概念,专门用于解决此问题,它可以作为附加信息来区分不同库中相同名称的函数、类、变量等,使用了命名空间定义了上下文,本质上,命名空间就是定义了一个范围。
- 定义:
namespace 命名空间名{}
- 使用:
命名空间名::函数()/变量
- using指令:
加上
using namespace 命名空间名
,使用变量的时候就不用在前面加上命名空间的名称,这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
- 嵌套命名空间:
using namespace 父命名空间::子命名空间
1.2基本的输入输出
C++ 标准库提供了一组丰富的输入/输出功能,我们将在后续的章节进行介绍。本章将讨论 C++ 编程中最基本和最常见的 I/O 操作。C++ 的 I/O 发生在流中,流是字节序列。如果字节流是从设备(如键盘、磁盘驱动器、网络连接等)流向内存,这叫做输入操作。如果字节流是从内存流向设备(如显示屏、打印机、磁盘驱动器、网络连接等),这叫做输出操作。
1.2.1I/O 库头文件
<iostream>
: 该文件定义了 cin、cout、cerr 和 clog 对象,分别对应于标准输入流、标准输出流、非缓冲标准错误流和缓冲标准错误流。<iomanip>
:该文件通过所谓的参数化的流操纵器(比如 setw 和 setprecision),来声明对执行标准化 I/O 有用的服务。<fstream>
: 该文件为用户控制的文件处理声明服务。
1.2.2常用函数:
- 标准输出流(cout)
预定义的对象 cout 是 iostream 类的一个实例。cout 对象"连接"到标准输出设备,通常是显示屏。实例如下:
cout << "我" << "是你爸爸"<< ....
,当然你还可以使用endl
结束当前行输出。
- 标准输入流(cin)
预定义的对象 cin 是 iostream 类的一个实例。cin 对象附属到标准输入设备,通常是键盘。cin 是与流提取运算符 >> 结合使用的,实例所示:
cin >> name >> age;
- 标准错误流(cerr)和标准错误流(cerr)
使用 cerr 流来显示错误消息,而其他的日志消息则使用 clog 流来输出。 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。 cerr 对象是非缓冲的,且每个流插入到 cerr 都会立即输出。
2.程序的编译运行
2.1编译
将C++程序编译成机器码,最直观的方式是会在你的项目的debug目录下创建一个
项目文件名.obj
文件
2.2链接
3.注释
- 单行注释://
- 多行注释:/**/
4.代码改进-简单的输入和输出
启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”,再向代码最后输入
std::cin.get();
这里的cin跟cout刚好相反,它是一个输入流对象。调用它内部的函数get(),就可以读取键盘的输入;等待键盘输入的时候,窗口就会一直开着。这里的键盘输入是以回车作为结束标志的,所以运行看到结果之后,直接敲回车就可以退出了。
5.函数
需要注意的一点是,如果方法写到主函数后面那么就需要的在文件开头定义一下
6.变量
在C++中使用变量需要初始化,不使用的变量就不需要了
语法:变量类型 变量名 = 初始值;
7.标识符
标识符就是变量、函数(也就是java中的方法)的名字,
规范:
- 不能使用C++关键字
- 不能用连续的两个下划线开头,不能以下划线加大写字母开头
- 函数体外的标识符,不能以下划线开头
- 命名要有实际意义
- 变量名一般用小写字母
- 自定义类型以大写字母开头
- 如果包含多个单词,一般用下划线分割,或者后面的单词首字母大写(驼峰)
常见的关键字:
8.变量作用域
- 局部作用域:定义在函数中的变量
- 自动对象
平常代码中定义的普通局部变量,生命周期为:在程序执行到变量定义语句时创建,在程序运行到当前块末尾时销毁。这样的对象称为“自动对象”。
形参也是一种自动对象。形参定义在函数体作用域内,一旦函数终止,形参也就被销毁了。- 静态对象
如果希望延长一个局部变量的生命周期,让它在作用域外依然保留,可以在定义局部变量时加上static关键字;这样的对象叫做“局部静态对象”。
局部静态对象只有局部的作用域,在块外依然是不可见的;但是它的生命周期贯穿整个程序运行过程,只有在程序结束时才被销毁,这一点与全局变量类似
- 全局作用域:定义在所有函数外的变量
一般情况下,如果出现相同名称变量,那个作用域小,那个生效,如果想用全局作用域,需要在变量上加上
::
,例如:::num
9.常量
不会变的变量,在C++中有两种定义方式
#define
和const
修饰符,跟使用 #define定义宏常量相比,const定义的常量有详细的数据类型,而且会在编译阶段进行安全检查,在运行时才完成替换,所以会更加安全和方便。
- 使用
#define
定义,保留了C语言中定义的格式,但是C++不推荐
#define ZERO 0
- 使用
const
限定符定义,const修饰的对象一旦创建就不能改变,所以必须初始化
const Zero = 0
10.基本数据类型
定义变量时,不可获取的一个要素就是数据类型,本质上来讲,这就是为了实现计算要求,我们必须先定义好数据的样式,告诉计算机这些数据占多大的空间,这就是所谓的数据类型的含义
10.1整形
在计算机中,所有的数据都是以二进制0,1来表示的,每个叫做一位,计算机可寻址的内存最小单位是8位,也就是一个字节,所以我们要范文的数据都是保存在内存的一个个字节中的。在C++中定义了个五种整型
char
,short
,int
,long
,long long11
,下面就是常用的类型的字节数
但是,C++对这些类型占据的长度定义比较灵活,可以由操作系统自己定义,但是C++也是有要求的,如下。在一般的操作系统中,short为16位字节,long为32位,long long为64位。但是int有所不同,但是window7 window10 macos中int都是32位的。
- short至少为两位字符
- int至少为两位,且不能比short短
- long至少为4位,不能比int短
- long long至少为8位,不能比long短
10.2 sizeof关键字
用来判断当前变量占据的字节数,例如
sizeof i
10.3无符号整型
在我们程序的使用过程中,正常会遇到非负数,此时就需要无符号整型
- 语法:
unsigned 数据类型 变量名;
- 注意点:
- 一般的整数计算都用int
- 如果超过了int的表示范围,就用long long
- 确定数值不可为负数,就用无符号类型
- 当数值超出了整形能够表示的范围,程序本身并不会报错,而是会让数据回到表示的最小值,这种情况叫做"数据溢出"(或者算术溢出)",写程序的时候一定要避免。
10.4char类型
如果我只需要处理很小的数字,也可以使用char,通常只占用8个字节,不过char经常用来表示字符。计算机底层都是由二进制位来表示的,将这些数字与字符、符号关联起来的过程,就是编码。我们常用的就是ASCII编码,在程序中如果使用的是char类型的变量,打印出来的是字符,实际底层是一个整数。如果我想要表示小于128的数字,只需要将数据类型改为int就行了。
常用的字符类型:
- char(8位)
- wchar(16位)
- char16_t(16位)
- char32_t(32位)
10.5bool类型
和java中的boolean一样,占据8位bit,true真,底层就是1,flase就是假,底层就是0
10.6浮点类型
浮点类型分类两种,分别为float和double,folat的位数不能小于double,通常float占据4个字节,double占据8个字节,float有6位有效数字,double有15位有效数字。同时C++还提供了一种高精度的浮点类型long double,一般占据12/16个字节。
- 科学计数法:
也成为E计数法,例如:5.98E24表示5.98×1024;9.11e-31表示9.11×10-31。
10.7字面值常量
我们在给一个变量赋值的时候,会直接写一个整数或者小数,这个数据就是显式定义的常量值,叫做“字面值常量”,我们在给一个变量赋值的时候,会直接写一个整数或者小数,这个数据就是显式定义的常量值,叫做“字面值常量”,
10.7.1整数型字面值
整型字面值就是我们直接写的一个整数,比如30。这是一个十进制数。而计算机底层是二进制的,所以还支持我们把一个数写成八进制和十六进制的形式。以0开头的整数表示八进制数;以0x或者0X开头的代表十六进制数。例如:
30 十进制,036 八进制, 0x1E十六进制
,在C++中,一个整型字面值,默认就是int类型,前提是数值在int能表示>的范围内。如果超出int范围,那么就需要选择能够表示这个数的、长度最小的那个类型。默认什么都不加,是int类>型;l或者L,表示long类型;ll或者LL,表示long long类型;u或者U,表示unsigned无符号类型;
10.7.2浮点型字面值
可以用一般的小数或者科学计数法表示的数,来给浮点类型赋值,这样的数就都是“浮点型字面值”。浮点型字面值默认的类型是double。如果我们希望明确指定类型,也可以加上相应的后缀:
f或者F,表示float类型;l或者L,表示long double类型
10.7.3字符或字符串字面值
字符就是我们所说的字母、单个数字或者符号,字面值用单引号引起来表示。字符字面值默认的类型就是char,底层存储也是整型。而多个字符组合在一起,就构成了“字符串”。字符串字面值是一串字符,用双引号引起来表示。字符串是字符的组合,所以字符串字面值的类型,本质上是char类型构成的“数组”(array)。
- 转义字符
有一类比较特殊的字符字面值,我们是不能直接使用的。在ASCII码中我们看到,除去字母、数字外还有很多符号,其中有一些本身在C++语法中有特殊的用途,比如单引号和双引号;另外还有一些控制字符。如果我们想要使用它们,就需要进行“转义”,这就是“转义字符”。
10.7.4布尔字面值
只有两个:true和false。
10.8修饰符类型
C++允许在char、int和double数据类型前放置修饰符,修饰符是用于改变变量类型的行为的关键字,他能满足各种情境的需求,常用的有如下几个:
- signed:表示变量可以存储负数,对于int类型变量来说,signed是默认的,可以省略
- unsigned:表示变量不能存储负数,对于整数来说,由原来的
-32768~32767
变为:0~65535
- short:表示变量的范围比 int 更小。short int 可以缩写为 short。
- long:表示变量的范围比 int 更大。long int 可以缩写为 long。
- long long:表示变量的范围比 long 更大。C++11 中新增的数据类型修饰符。
10.9类型限定符
- const:表示变量的值不能被修改
- volatile:变量的值可以被程序以外的因素改变,如硬件或其他线程
- restrict:指针是访问它所在对象的唯一方式,只在C99中增加了
- mutable:表示类中的程序可以在const成员函数中被修改
- static:定义静态变量,作用域为当前文件或当前函数
- register:定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,提高程序运行的效率
11.存储类
存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前
- auto:自 C++ 11 以来,auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。但由于使用极少且多余,在 C++17 中已删除这一用法。
- register:register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
- static:static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
- extern:提供一个全局变量/函数的引用,当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用
- thread_local:使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
11.类型转换
在C++中,不同类型的数据对象,是可以放在一起做计算的。这就要求必须有一个机制,能让有关联的两种类型可以互相转换。
11.1隐式类型转换
在C++中,不同类型的数据对象,是可以放在一起做计算的。这就要求必须有一个机制,能让有关联的两种类型可以互相转换。
- 在大多数算术运算中,较小的整数类型(如bool、char、short)都会转换成int类型。这叫做“整数提升”;(而对于wchar_t等较大的扩展字符类型,则根据需要转换成int、unsigned int、long、unsigned long、long long、unsigned long long中能容纳它的最小类型)
- 当表达式中有整型也有浮点型时,整数值会转换成相应的浮点类型;
- 在条件判断语句中,其它整数类型会转换成布尔类型,即0为false、非0为true;
- 初始化变量时,初始值转换成变量的类型;
- 在赋值语句中,右侧对象的值会转换成左侧对象的类型;
- 要尽量避免将较大类型的值赋给较小类型的变量,这样很容易出现精度丢失或者数据溢出
11.2强制类型转换
除去自动进行的隐式类型转换,我们也可以显式地要求编译器对数据对象的类型进行更改。这种转换叫做“强制类型转换”(cast)。
- 语法:
- C语言风格:
(类型) 值;
例如:(int) 1.111111D
- C++函数风格:
类型(值);
例如:int(1.1111D)
- C++强制类型转换运算符:
static_cast<类型名称>(值)
例如:static_cast<int>(1.1111D)
- C语言风格:
强制类型转换会干扰正常的类型检查,带来很多风险,所以通常要尽量避免使用强制类型转换。
12.运算符
12.1算数运算符
12.2赋值运算符
- 常用的:
=
,例如:int i = 1
- 符合运算符:
+=, -=, *=, /=, %=
12.3 递增递减运算符
C++为数据对象的“加一”“减一”操作,提供了更加简洁的表达方式,这就是递增和递减运算符(也叫“自增”“自减”运算符)。“递增”用两个加号“++”表示,表示“对象值加一,再赋值给原对象”;“递减”则用两个减号“–”表示
前置时,对象先加1,再将更新之后的对象值作为结果返回;后置时,对象先将原始值作为结果返回,再加1;
12.4关系和逻辑运算符
逻辑与和逻辑或有两个运算对象,在计算时都是先求左侧对象的值,再求右侧对象的值;如果左侧对象的值已经能决定最终结果,那么右侧就不会执行计算:这种策略叫做“短路求值”;
12.5条件运算符
语法:
条件判断表达式 ? 表达式1 : 表达式2
,也就是java中的三元表达式
12.6位运算符
注意点:
- 较小的整数类型(char、short以及bool)会自动提升成int类型再做移位,得到的结果也是int类型
- 左移运算符“<<”将操作数左移之后,在右侧补0;
- 右移运算符“>>”将操作数右移之后,对于无符号数就在左侧补0;对于有符号数的操作则要看运行的机器环境,有可能补符号位,也有可能直接补0;
- 由于有符号数右移结果不确定,一般只对无符号数执行位移操作;
- 按位取反“~”:一元运算符,类似逻辑非。对每个位取反值,也就是把1置为0、0置为1;
- 位与“&”:二元运算符,类似逻辑与。两个数对应位上都为1,结果对应位为1;否则结果对应位为0;
- 位或“|”:二元运算符,类似逻辑或。两个数对应位上只要有1,结果对应位就为1;如果全为0则结果对应位为0;
- 位异或“^”:两个数对应位相同,则结果对应位为0;不同则结果对应位为0;
13.条件分支
- if
if (bool表达式) {
} else if (bool表达式) {
} else {
}
- case
switch(值) {
case 值1:
...
break;
case 值2:
...
break;
case 值3:
...
break;
}
14.循环
- while
while(bool表达式) {
...
}
- do-while
do {
...
}
while (bool表达式)
注意点:do-while函数会先执行do里面的程序,在进行判断
- for
for (初始化语句; 条件; 表达式) {
...
}
- 范围for循环(类似java中的foreach,果然java是抄的C++)
for (声明: 序列表达式) {
...
}
15.跳转
- break
跳出当前控制语句
- continue
忽略本次循环
- go to
无条件地跳转到程序的标签位置,如下例子当执行到go to语句,程序将重新从begin执行。
begin:
do
{
cout << " x = " << ++x << endl;
} while (x < 10);
if (x < 15) {
cout << "回到原点!" << endl;
goto begin;
}
cout << "程序结束!" << endl;
- return
return是用来终止函数运行并返回结果的
16.复合数据类型
16.1数组
- 数组的定义
数据类型 数组名[元素个数];
- 数组的初始化
数据类型 数组名[元素个数] = {....}
需要注意的是元素个数不写,但是不能超过值的个数,如果个数大于值的个数-,其余的由默认值填充 - 数组的访问
数组名[元素下标]
- 数组的长度(说实话,比较粗暴)
sizeof(数组)/sizeof(第一个数组元素)
- 多维数组
实际就是靠数组中嵌套数组实现的,比如
a[2][2]
,就是一个长度为2的数组,每个元素都是一个长度为2的数组
16.2Vector
数组尽管很灵活,但使用起来还是很多不方便,比如数组的长度是不能变的。为此,C++语言定义了扩展的“抽象数据类型”(Abstract Data Type, ADT),放在“标准库”中。
- 使用方法
#include <iostream>
#include <vector>
using namespace std;
int main() {
//1.初始化
vector<int> v1;
//2.添加
v1.push_back(1);
//3.修改
v1[0] = 2;
//4.便利
for (int i = 0; i < v1.size(); i++) {
}
}
16.3Array
上面的Vector主要用于长度不确定的集合,而Array就是用与长度确定的集合,至此,我们就可以不用使用数组了。这个玩意还是指针学完再研究
16.4字符串
- 定义:
//无初始化定义
string j;
//拷贝初始化
string i = "Hello Word";
//直接初始化
string k("Test");
//通过重复次数初始化
string l("1", 8);
- 字符串相加
string i = "Hello";
string j = "Word";
string k = i + j;
注意点
- 两个字符串常量值不能直接拼接,例如:
string k = "Hello" + "Word";
- 多个string对象和多个字符串字面值常量,可以连续相加;前提是按照左结合律,每次相加必须保证至少有一个string对象
- 比较字符串
string类还提供几种用来做字符串比较的运算符,“==”和“!=”用来判断两个字符串是否完全一样;而“<”“>”“<=”“>=”则用来比较两个字符串的大小。这些都是关系型运算符的重载。C++中字符串的比较是从第一个字符开始比较
- 读出控制太输入的一行字符串
//由于cin忽略开始的空白符,遇到下一个空白符(空格、回车、制表等)就会停止
//而getline可以获取一行的字母串
getline(cin, str);
16.5文件的写入与读取
#include <iostream>
#include <string>
#include <fstream>
using namespace std;
int main() {
ofstream writeFile;
string content;
string inputContent;
//设置当前文档为读取模式
writeFile.open("C:\\Users\\Mick\\Desktop\\1.txt", ios::app);
//读取写入内容
cout << "place input content:";
getline(cin, content);
//写入文件
writeFile << content << endl;
//关闭文件流
writeFile.close();
//读出内容
ifstream readFile;
cout << "当前输入内容是:";
readFile >> content;
cout << content << endl;
//关闭文件流
readFile.close();
cin.get();
cin.get();
return 0;
}
16.6结构体
实际应用中,我们往往希望把很多不同的信息组合起来,“打包”存储在一个单元中。比如一个学生的信息,可能包含了姓名、年龄、班级、成绩…这些信息的数据类型可能是不同的,所以数组和vector都无法完成这样的功能。
C/C++中提供了另一种更加灵活的数据结构——结构体。结构体是用户自定义的复合数据结构,里面可以包含多个不同类型的数据对象。
- 语法(一定要主要这个
;
,java中定义类是不需要的)
struct 结构体名
{
类型1 数据对象1;
类型2 数据对象2;
类型3 数据对象3;
…
};
- 初始化
如果没有赋初始值,那么所有数据将被初始化为默认值;
//创建对象并初始
studentInfo stu = {11, "男", "清华大学"};
//结构初始化直接使用(不推荐)
struct studentInfo {
int age;
string sex;
string address;
}stu1 = {10, "女", "地址"};
- 结构体变量访问
语法:
结构体变量.成员的名称
//修改数据
stu.age = 12;
//读取数据
cout << stu.age << endl;
- 结构体数组
// 结构体数组
studentInfo s[2] = {
{"小红", 18, 92},
{"小白", 20, 82}
};
- 结构体中的位字段
用于指定结构中的变量的指定位数
- 语法:
类型 变量名 : 数字
,例如:int age : 4;
代表age
的值只能是4位
16.7共用体
与结构体不同的是
共用体
,一个结构体由其中的多个变量组成,就像java中的类一样,但是共用体是不同的,它只能是多个变量中的一个,如下:
union nu
{
int a;
double b;
long c;
}
此时nu只能是int/double/long类型中的一种,但是底层存储的时候用的是最大的类型
16.8枚举
有限个常量值的集合
- 定义(枚举量默认从0开始,每个枚举量依次加1)
enum 变量名 {
One,
Twe = 2,
.....
};
在Java中可以自定义枚举中成员变量的结构,而在C++中不能,只能是一个整数。
- 获取枚举
枚举类型 变量名 = 成员常量;
16.9指针
指针是C/C++中一种特殊的数据类型,它所保存的信息,其实是另外一个数据对象在内存中的“地址”。通过指针可以访问到指向的那个数据对象,所以这是一种间接访问对象的方法。
- 定义(&被称为取地址符,*解引用操作符是用来通过地址获取变量的)
数据类型* 变量名 = &变量;
- 指针所占用的内存空间:32位操作系统是4个字节,64位是8个字节
- 无效指针
定义一个指针之后,如果不进行初始化,那么它的内容是不确定的(比如0xcccc)。如果这时把它的内容当成一个地址去访问,就可能访问的是不存在的对象;更可怕的是,如果访问到的是系统核心内存区域,修改其中内容会导致系统崩溃。这样的指针就是“无效指针”,也被叫做“野指针”。
int* p1; // 危险!指针没有初始化,是无效指针
指针非常灵活非常强大,但野指针非常危险。所以建议使用指针的时候,一定要先初始化,让它指向真实的对象。
- 空指针
如果先定义了一个指针,但确实还不知道它要指向哪个对象,这时可以把它初始化为“空指针”。空指针不指向任何对象。
//指针的定义
int* i = nullptr;
int* j = 0;
int* k = NULL;
空指针所保存的其实就是0值,一般把它叫做“0地址”;这个地址也是内存中真实存在的,所以也不允许访问。
- void*指针
一般来说,指针的类型必须和指向的对象类型匹配,否则就会报错。不过有一种指针比较特殊,可以用来存放任意对象的地址,这种指针的类型是void*,void* 指针表示只知道“保存了一个地址”,至于这个地址对应的数据对象是什么类型并不清楚。所以不能通过
*变量名
指针访问对象;一般 void* 指针只用来比较地址、或者作为函数的输入输出。
- 指向指针的指针
指针本身也是一个数据对象,也有自己的内存地址。所以可以让一个指针保存另一个指针的地址,这就是“指向指针的指针”,有时也叫“二级指针”;形式上可以用连续两个的星号
**
来表示。类似地,如果是三级指针就是***
,表示“指向二级指针的指针”。
- 指针和const
指针可以和const修饰符结合,这可以有两种形式:一种是指针指向的是一个常量;另一种是指针本身是一个常量。
指针常量
int* const pointer = &i;
不能修改指针指向的地址常量指针
const char* a;
不能改指针所指内容,可以改地址常指针常量
const char* const a
内容和地址都不能修改
- 指针和数组
我们知道打印一个数组的时候输出的是数组的地址,也就是数组中第一个数据的起始地址,因此我们可以通过
*
来操作数组,但是需要注意的是,对一个地址做运算,比如地址 + 1
就是相对与当前地址的下一个地址。
16.10引用
就是变量的别名,类似于SQL语言中的AS,引用是不占用内存空间的,本质就是指针常量,一旦被初始化,不可改变。(我在这吐槽一句:尚硅谷的C++讲的一般,看完引用,我看到&&依然不知道是啥)
16.10.1左值和右值
在 C++ 或者 C 语言中,一个表达式(可以是字面量、变量、对象、函数的返回值等)根据其使用场景不同,分为左值表达式和右值表达式。确切的说 C++ 中左值和右值的概念是从 C 语言继承过来的。位于赋值号(=)左侧的表达式就是左值;反之,只能位于赋值号右侧的表达式就是右值。
16.10.2左值引用(&)
- 语法
数据类型& 引用名 = 变量名/引用名;
注意:只能给变量/引用
设置引用,操作引用就是操作变量,引用绑定了后就不能修改指向的变量,不能在=
写常量,当然可以通过常量引用的方法
- 常量引用
- 语法:
const 数据类型& 引用名 = 常量;
- 因为引用的对象是常量,所以常量引用是不能修改的
16.10.3右值引用(&&)
右值往往是没有名称的,因此要使用它只能借助引用的方式。这就产生一个问题,实际开发中我们可能需要对右值进行修改(实现移动语义时就需要),显然左值引用的方式是行不通的。为此,C++11 标准新引入了另一种引用方式,称为右值引用,用 “&&” 表示
- 语法:
数据类型&& 引用名 = 常量
(注意右值引用可以修改常量,但是右值引用=
的右侧不能是变量,当然可以通过std::move()
获取左值的右值,从而实现)
16.11.4 引用和指针常量
引用和指针常量功能是类似的,但是由于引用就是变量的别名,不占用内存,而指针常量是占据内存的
16.11.5 指针的引用
- 语法:
数据类型*& 引用名 = 指针名称
。
17.函数
函数其实就是封装好的代码块,并且指定一个名字,调用这个名字就可以执行代码并返回一个结果。
- 格式:
返回类型 函数名(参数列表) { .... }
17.1 函数声明
如果我们将一个函数放在主函数后面,就会出现运行错误:找不到标识符。这是因为函数和变量>一样,使用之前必须要做声明。函数只有一个定义,可以定义在任何地方;如果需要调用函数,>只需要在调用前做一个声明,告诉编译器“存在这个函数”就可以了。
函数声明的方式,和函数的定义非常相似;区别在于声明时不需要把函数体写出来,用一个分号>替代就可以了。
- 分离式编译和头文件
- 当程序越来越复杂,我们就会希望代码分散到不同的文件中来做管理。C++支持分离式编译,这就可以把函数单独放在一个文件,独立编译之后链接运行。通常的做法是将函数抽离为一个cpp文件,然后建立这个cpp对应头文件(.h),需要注意的是自己创建的文件,引入的是时候用
""
,库文件用<>
17.2 参数传递
- 语法:函数在每次调用时,都会重新创建形参,并且用传入的实参对它进行初始化。形参的类型,决定了形参和实参交互的方式;也决定了函数的不同功能。参数传递和变量的初始化类似,对一个变量做初始化,如果用另一个变量给它赋初值,意味着值的拷贝;也就是说,此后这两个变量各自一份数据,各自管理,互不影响,但是需要注意的是数组是不能够做拷贝的,因为数组本质上就是一个指针。而如果是定义一个引用,绑定另一个变量做初始化,并不会引发值的拷贝;引用和原变量管理的是同一个数据对象。这一点还是和java不太一样,java如果参数是引用,那么会直接改变地址,类似于参数是指针
- 分类:
- 值传参:形参是基本数据类型,
add(a)
,a并不会增加:- 指针传参:形参是指针类型,
add(&a)
,a会增加
- 引用传参:形参是引用类型,
add(a); //add(int& a)
,a会增加- 数组传参:
void printArray(const int(&arr)[6])
{
for (int num : arr)
cout << num << "\t";
cout << endl;
}
17.3 可变形参
有时候我们并不确定函数中应该有几个形参,这时就需要使用“可变形参”来表达。
C++中表示可变形参的方式主要有三种:
- 省略符(…):兼容C语言的用法,只能出现在形参列表的最后一个位置;
- 初始化列表initializer_list:跟vector类似,也是一种标准库模板类型;initializer_list对象中的元素只能是常量值,不能更改;
- 可变参数模板:这是一种特殊的函数,后面会详细介绍。
17.4 返回类型
函数可以通过return语句,终止函数的执行并“返回”函数调用的地方;并且可以给定返回值。返回>值的类型由函数声明时的“返回类型”决定。return语句可以有两种形式:
return;
和return 返回值;
- 无返回值:
当函数返回类型为void时,表示函数没有返回值。可以在函数中需要返回时直接执行 return语句,也可以不写。因为返回类型为void的函数执行完最后一句,会自动加上return返回。
- 有返回值:
如果函数返回类型不为void,那么函数必须执行return,并且每条return必须返回一个值。返回值的类型应该跟函数返回类型一致,或者可以隐式转换为一致。如果函数返回类型不为void,那么函数必须执行return,并且每条return必须返回一个值。返回值的类型应该跟函数返回类型一致,或者可以隐式转换为一致。例如:
const string& longerStr(const string& str1, const string& str2)
需要注意的是函数返回引用类型时,不能返回局部对象的引用;同样道理,也不应该返回指向局部对象的指针。
17.5 链式调用
如果函数返回一个类的对象,那么我们可以继续调用这个对象的成员函数,这样就形成了“链式调用”。例如:
longerStr(str1, str2).size();
调用运算符,和访问对象成员的点运算符优先级相同,并且满足左结合律。所以链式调用就是从左向右依次调用,代码可读性会更高。
- 主函数的返回值
主函数main是一个特殊函数,它是我们执行程序的入口。所以C++中对主函数的返回值也有特殊的规定:即使返回类型不是void,主函数也可以省略return语句。如果主函数执行到结尾都没有return语句,编译器就会自动插入一条:
return 0;
主函数的返回值可以看做程序运行的状态指示器:返回0表示运行成功;返回非0值则表示失败。非0值具体的含义依赖机器决定。这也是为什么之前我们在主函数中都可以不写return
17.6 数组指针和指针数组
- 数组指针:数据类型(*变量名)[数组容量];例如:
int(*a1)[8];
- 指针数组: 数据类型* 变量名[数组容量];例如:
int* a1[8];
17.7 数组引用和引用数组
- 数组引用:数据类型(&变量名)[数据容量];例如:
int(&a)[2]
- 引用数组:数据类型& 变量名[数据容量];例如:
int& a[2]
17.8 声明返回值为数组指针的函数
与形参的讨论类似,由于数组“不能拷贝”的特点,函数也无法直接返回一个数组。同样的,我们可以使用指针或者引用来实现返回数组的目标;通常会返回一个数组指针。
- 语法:
数据类型(*函数名(形参列表))[数组容量);
就是数组指针定义中把变量名换成函数声明- 也可以用以下两种方式定义:
typedef:
typedef int arrayT[5]; // 类型别名,arrayT代表长度为5的int数组
arrayT* fun2(int x); // fun2的返回类型是指向arrayT的指针
尾置返回类型
auto fun3(int x) -> int(*)[5];
17.9递归
不说了,学java的递归应该很熟悉了
18.函数高阶
18.1内敛函数
定义内联函数,只需要在函数声明或者函数定义前加上inline关键字,内联函数不会像正常函数调用,直接进入,而是会把函数的内容,直接复制粘贴到调用处。内联函数是C++新增的特性。在C语言中,类似功能是通过预处理语句#define定义“宏”来实现的。
然而C中的宏本身并不是函数,无法进行值传递;它的本质是文本替换,我们一般只用宏来定义常量。用宏实现函数的功能会比较麻烦,而且可读性较差。所以在C++中,一般都会用内联函数来取代C中的宏。
需要注意的是:: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!另一个实用的经验准则: 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
18.2默认实参
在有些场景中,当调用一个函数时它的某些形参一般都会被赋一个固定的值。为了简单起见,我们可以给它设置一个“默认值”,这样就不用每次都传同样的值了。
- 语法:
返回值 函数名(参数类型 变量名=默认值,.....);
,例如:int add(int a, int b = 0);
- 调用:
函数名(实参...)
,如果没有传入参数,将自动采用默认值。
18.3函数重载
在C++中,同一作用域下,同一个函数名是可以定义多次的,前提是形参列表不同。这种名字相同但形参列表不同的函数,叫做“重载函数”。这是C++相对C语言的重大改进,也是面向对象的基础。java中已经学过,我就不说了
需要注意的是:=函数重载必须在同一个作用域下,函数名称必须相同,(参数类型,个数不同,顺序不同)都可实现函数重载,但是函数返回值不可以作为重载条件
- const和重载
- 形参类型是变量,添加const,形参依然是变量,指定传入的实参依然是变量,所以不会重载:
- 形参类型是指针,添加const,形参是指针常量,指定传入的实参依然是变量,不会触发重载
- 形参类型是指针,添加const,形参是常量指针,指定传入的实参必须是常量,会触发重载
- 形参类型是引用,添加const,形参是常熟,指定从传入的实参必须是常量,会触发重载
- 默认参数和重载
如果函数重载碰到默认参数的类型,比如
int fun1(int a)
和int fun2(int a, int b=10)
,但是当我们使用fun1(10)
,不知道调用那个。因此这种语法是不被允许的
18.4函数的作用域
重载是否生效,跟作用域是有关系的。如果在内层、外层作用域分别声明了同名的函数,那么内层作用域中的函数会覆盖外层的同名实体,让它隐藏起来。
18.5函数占位参数
C++中函数的形参列表里可以有占位参数,用来占位,调用函数的时候必须填补该位置。
- 语法:返回值类型 函数名 (数据类型=初始值){}注意占位参数名不要写
18.5函数指针
- 语法:返回值(函数指针变量名*)(参数类型1,参数类型2…) = 函数名;
- 使用decltype定义:
typedef decltype(函数名) 变量名;
19.内存分区模型
- 代码区:存放函数体的二进制代码,有操作系统进行管理
- 全局区:存放全局变量和静态变量以及常量
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量
- 堆区:由程序员分配和释放,若程序员不释放,程序结束由操作系统回收
按照程序运行的顺序,我们将程序分为两部分,分别为程序员运行前和程序运行后
- 程序运行前包含代码区和全局区:
- 代码区:存放CPU执行的机器指令,共享,只读的
- 全局区:存放全局变量、静态变量、常量,代码执行后由操作系统自行释放。
- 程序执行后
- 栈区:由编译器自动分配释放,存放函数的参数值,局部变量
- 堆区:由程序员分配释放,C++中通过new开辟内存,程序执行完如果没有手动关闭,将有操作系统自动关闭
20.new关键字
用于在堆区中开辟数据,由程序员手动开辟,手动释放采用
delete
关键字,这里需要注意一点正常情况下,使用类名 变量名
创建的变量存在栈区,由C++自动释放,而new出来的是手动释放
- 语法:数据类型* 变量名 = new 数据类型;例如:
int* a = new int(初始值)
;
21.类与对象
21.1类的定义:
- 语法:
class 类名{访问权限:属性/行为}
类中的成员函数可以直接写成函数声明,当然我常这样做,然后用::
在类定义后进行实现 - 例如:
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
21.2类的声明(类对象和类指针)
- 类对象:
类名 变量名
,例如:Box b1(初始参数列表)
,如果初始参数列表为空,要写成Box b1
. - 类指针:
类名* 指针名 = new 类名()
,例如:Box* b1 = new Box(初始参数列表)
,我的理解是类指针,就是指向类的指针,切记类指针必须要赋值,但是类对象不需要;
21.3类的成员函数
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
正常情况下,在java中,函数的实现是写到内部,如下:
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
但是在c++中,函数的实现可以写到外边的,只要函数的声明写到类中,不得不说,太牛了,例子如下(推荐)但是总有些大胆的哥们,想要尝试在类中写类的声明,再用::
在类外定义,这时候编译器就会报错
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
};
double Box::getVolume()
{
return length * breadth * height;
}
21.4类访问修饰符
这一点和java还是挺像的
- public——公共权限——成员类内可以访问,类外可以访问
- protected–保护权限——成员类内可以访问,类外不可以访问,子类可以访问
- private——私有权限——成员类内可以访问,类外不可以访问,子类不可以访问
21.5构造函数和析构函数
构造函数和析构函数(就是java中的解构函数)我就不说了,因为在java中也有所以我就简单介绍下语法,同样,和java一样,如果不自定义实现,将由编译器自动实现,只不过是空实现,但是需要注意一点:当你自己定义一个构造函数的时候,编译器就不再生成默认构造函数了,除非你自己显式的定义,所以你如果定义了有参数的构造函数,然后却想无参数初始化,编译器会报错的,这点还是和java相同的
21.5.1带参数的构造函数
在Java中我们常常这样做:
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
Box(double len) {
cout << "Object is being created, length = " << len << endl;
length = len;
}
};
由于C++中提供了成员函数的特殊形式,也就是使用::
在类外对函数进行实现,所以代码就变成这样了
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
Box(double len);
};
Box::Box(double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
但是C++还提供了初始化列表进行初始化字段,所以代码就变成这样了,不得不说,太帅了
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
Box(double len);
};
Box::Box(double len) : length(len)
{
cout << "Object is being created, length = " << len << endl;
}
实际我们想一想,初始化列表就是向你的方法体中添加一个语句,如上个代码,是和下面的代码相同的
class Box {
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void);
Box(double len);
};
Box::Box(double l, double b) : length(l), breadth(b)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
21.5.2析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
- 语法:
~类名();
21.6拷贝构造函数
拷贝构造函数是一种特殊的构造函数,他在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象,如果类中没有定义拷贝构造函数,编译器会自定义一个,但是如果类中带有指针变量,并由动态内存分配,则它必须有一个拷贝构造函数。
- 语法:
类名 (const class &obj){}
21.7友元函数
类的友元函数(友元类)是声明在类的内部,定义在类的外部,如果友元函数(友元类)的是实参中存在当前类型的变量,那么就可以直接该类中的成员变量/成员函数
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth(Box box);
friend class BigBox;
void setWidth(double wid);
};
class BigBox
{
public :
void Print(int width, Box &box)
{
// BigBox是Box的友元类,它可以直接访问Box类的任何成员
box.setWidth(width);
cout << "Width of box : " << box.width << endl;
}
};
// 成员函数定义
void Box::setWidth(double wid)
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth(Box box)
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width << endl;
}
// 程序的主函数
int main()
{
Box box;
BigBox big;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth(box);
// 使用友元类中的方法设置宽度
big.Print(20, box);
getchar();
return 0;
}
21.8this指针
在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。
这里扩展一个小知识,在C++中,如果想要从实体(对象)中获取成员变量/成员函数用.
,如果想要从对象的指针中获取成员变量/成员函数用成员访问运算符->
,比如this指针就要用成员访问运算符->
#include <iostream>
#include <string>
using namespace std;
class Box {
public:
double length;
double breadth;
double height;
void showInfo();
};
void Box::showInfo() {
cout << this->length << "," << this->height << "," << this->breadth << endl;
}
int main() {
Box b1;
b1.breadth = 1.0;
b1.height = 2.0;
b1.length = 3.0;
b1.showInfo();
cin.get();
return 0;
}
21.9C++的静态变量和静态函数
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
21.9.1静态变量
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
#include <iostream>
#include <string>
using namespace std;
class Box {
public:
double length;
double breadth;
double height;
static int count;
void showInfo();
};
int Box::count = 2;
int main() {
cout << Box::count << endl;
cin.get();
return 0;
}
21.9.2静态函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。静态成员函数有一个类范围,他们不能访问类的 this 指针。需要注意的是C++中,静态函数是可以在类中定义的,这点和静态变量不能在类中初始化还是有区别的
#include <iostream>
#include <string>
using namespace std;
class Box {
public:
double length;
double breadth;
double height;
static int count;
void showInfo();
static void sayHello() {
cout << "Hello" << endl;
}
};
int Box::count = 2;
int main() {
Box::sayHello();
cin.get();
return 0;
}
21.10封装
封装,我就不讲了,就是把成员变量封装起来,提供get、set方法,使程序不能直接操作成员变量,和java相同
21.11继承
继承允许我们依据另一个类来定义一个类,这使得创建和维护一个应用程序变得更容易。这样做,也达到了重用代码功能和提高执行效率的效果。当创建一个类时,您不需要重新编写新的数据成员和成员函数,只需指定新建的类继承了一个已有的类的成员即可。这个已有的类称为基类,新建的类称为派生类。但是需要注意一点,C++支持多继承,并且和java一样,protected类型的类可以被派生类继承
- 语法:和Java不同,C++继承使用的是符号
:
,而java使用的是extend
,例如:class Stephen : public Person
,语法如下:class 派生类 : 继承类型 基类
21.11.1继承类型
当一个类派生自基类,该基类可以被继承为 public、protected 或 private 几种类型。
- 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
- 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
- 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
21.12重载运算符和重载函数
函数重载就不再记录了,具体可看
18.3
章节,
21.12.1重载运算符
您可以重定义或重载大部分 C++ 内置的运算符。这样,您就能使用自定义类型的运算符。
重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。
- 语法:
返回值类型 operator运算符(类名 变量){}
#include <iostream>
#include <string>
using namespace std;
class Box {
public:
double length;
double breadth;
double height;
double operator+(const Box& b) {
return this->length + b.length;
}
};
int main() {
Box b1;
b1.length = 1.0;
Box b2;
b2.length = 2.1;
cout << b1 + b2 << endl;
cin.get();
return 0;
}
21.13多态
多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
- 静态多态:如果父类中已经定义的方法,子类中也定义了,会依然使用父类的方法,这就是所谓的静态多态,或静态链接
- 虚函数:虚函数 是在基类中使用关键字
virtual
声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。 - 纯虚函数:如果不想再基类中定义函数的实现,而是由派生类中实现,也就这时候就需要用到纯虚函数,具体语法如下:
virtual 返回值 函数名(形参列表) = 0;
,= 0
告诉编译器,函数没有主体,为纯虚函数。
21.14接口(抽象类)
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类,也就是java中的抽象类。
22.异常处理
好消息是和java的相似,坏消息就是常用的异常好多
- 捕获异常:
try {
....
} catch(异常类名 e) {
....
}
- 抛出异常
throw 异常数据;
可以是 int、float、bool 等基本类型,也可以是指针、数组、字符串、结构体、类等聚合类型
同时,throw也可以用到函数的声明上,如int add() throw(exception,int)
,表示只可以捕获到exception
和int
类的异常,这就情况叫做异常规范,异常规范是 C++98 新增的一项功能,但是后来的 C++11 已经将它抛弃了,不再建议使用。
- 常用方法:
what() :异常类提供的一个公共方法,它已被所有子异常类重载。这将返回异常产生的原因
- 标准异常:
- 定义新的异常:
可以通过继承和重载 exception 类来定义新的异常。下面的实例演示了如何使用 std::exception 类来实现自己的异常:
23.多线程
23.1概念
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。
在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
有意思的是传统C++中是没有多线程的概念的,而是在C++11之后,才将boost中的C++的概念引进来的
23.2进程和线程
- 定义:进程是正在运行的程序的实例,而线程是进程中的实际运作单位
- 区别:
- 一个程序有且只有一个进程,但可以拥有至少一个线程
- 不同进程拥有不同的地址空间,互不相关,而不同线程共同拥有进程的公共空间
23.3std::thread
23.3.1构造函数和析构函数
- 默认构造函数:
thread() noexcept;
,语法:std::thread 变量名
首先先普及下
noexcept
的作用,在以往的版本中,如果想要表示一个函数没有一场抛出,可以使用void fun() throw()
,在C++11之后就可以用noexcept
表示,例如:void fun() noexcept
例子:
std::thread t1; //创建一个空thread对象,该对象非joinable
- 初始化构造函数:
template <class Fn, class… Args>
,语法:std::thread 变量名(函数,入参)
例子:
std::thread t1(f2, 1);
- 拷贝构造函数:
thread (const thread&) = delete;
,用法thread (const thread&) = delete;
被禁用,意味着thread对象不可拷贝构造
- move构造函数:
thread (thread&& x) noexcept;
,用法thread(std::move(变量)
例子:
thread t3(move(t2));
- 析构函数
~thread()
23.3.2常用成员函数
void join()
:等待线程结束并清理资源(会阻塞)bool joinable()
:返回线程是否可以执行join函数void detach()
:将线程与调用其的线程分离,彼此独立执行(此函数必须在线程创建时立即调用,且调用此函数会使其不能被join)std::thread::id get_id()
:获取线程id
23.3.3mutex和atomic
23.3.3.1mutex
std::mutex是 C++11 中最基本的互斥量,一个线程将mutex锁住时,其它的线程就不能操作mutex,直到这个线程将mutex解锁
- 常用方法
void lock()
:将mutex上锁,如果mutex已经被上锁,线程阻塞,直到解锁,如果被当前线程锁住,将会产生死锁void unlock()
:解锁bool try_look()
:查看当前互斥锁能否被上锁,true:能,fasle:不能
23.3.3.1atomic
原子操作是最小的且不可并行化的操作。
- 原子操作是最小的且不可并行化的操作。
atomic() noexcept = default
:构造一个atomic对象(未初始化,可通过atomic_init进行初始化)constexpr atomic(T val) noexcept
:构造一个atomic对象,用val的值来初始化atomic(const atomic&) = delete
:(已删除)
24日期类
C++ 标准库没有提供所谓的日期类型。C++ 继承了 C 语言用于日期和时间操作的结构和函数。为了使用日期和时间相关的函数和结构,需要在 C++ 程序中引用
<ctime>
头文件。clock_t、time_t、size_t 和 tm。类型 clock_t、size_t 和 time_t 能够把系统时间和日期表示为某种整数。结构类型 tm 把日期和时间以 C 结构的形式保存,tm 结构的定义如下:
struct tm {
int tm_sec; // 秒,正常范围从 0 到 59,但允许至 61
int tm_min; // 分,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月,范围从 0 到 11
int tm_year; // 自 1900 年起的年数
int tm_wday; // 一周中的第几天,范围从 0 到 6,从星期日算起
int tm_yday; // 一年中的第几天,范围从 0 到 365,从 1 月 1 日算起
int tm_isdst; // 夏令时
};
常用函数:
time_t time(time_t *time):
该函数返回系统的当前日历时间,自 1970 年 1 月 1 日以来经过的秒数。如果系统没有时间,则返回 -1,如果实参有值,会把返回值赋给实参。char *ctime(const time_t *time):
该返回一个表示当地时间的字符串指针,字符串形式 day month year hours:minutes:seconds year\n\0。struct tm *localtime(const time_t *time):
该函数返回一个指向表示本地时间的 tm 结构的指针。clock_t clock(void):
该函数返回程序执行起(一般为程序的开头),处理器时钟所使用的时间。如果时间不可用,则返回 -1。char * asctime ( const struct tm * time ):
该函数返回一个指向字符串的指针,字符串包含了 time 所指向结构中存储的信息,返回形式为:day month date hours:minutes:seconds year\n\0。struct tm *gmtime(const time_t *time):
该函数返回一个指向 time 的指针,time 为 tm 结构,用协调世界时(UTC)也被称为格林尼治标准时间(GMT)表示。time_t mktime(struct tm *time):
该函数返回日历时间,相当于 time 所指向结构中存储的时间。double difftime ( time_t time2, time_t time1 ):
该函数返回 time1 和 time2 之间相差的秒数。size_t strftime():
该函数可用于格式化日期和时间为指定的格式
25动态内存
C++中程序的内存分为两部分:
- 栈:在函数内部声明的所有变量都将占用栈内存,也就是通过
类名 变量名
定义的。- 堆:这是程序中未使用的内存,也是new出来的,在程序运行时可用于动态分配内存。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new 运算符。如果您不再需要动态分配的内存空间,可以使用 delete 运算符,删除之前由 new 运算符分配的内存。
25.1new
创建对象,分配堆中的地址,返回地址给变量
- 语法:
类名* 指针名 = new 类名(实参列表)
,如果实参列表为空,可以写成类名* 指针名 = new 类名;
需要注意一点:如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* pvalue = NULL;
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
25.2delete
delete 操作符释放变量所占用的内存
- 语法:
delete 指针名
26模板
模板是泛型编程的基础,泛型编程即以一种独立于任何特定类型的方式编写代码。就是java中的泛型,但是和java中的泛型的定义还是有很大区别的。
- 语法:
template <typename 类型名, typename 类型名, ....> 返回值类型 函数名(参数列表)
{
// 函数的主体
}
类型名一般用Java的就行,T:参数类型,R:返回值
- 例子:
#include <iostream>
#include <string>
using namespace std;
class Box {
public:
double length;
double breadth;
double height;
template<typename T, typename R> R add(T arg1, T arg2, R arg3);
};
template<typename T, typename R> R add(T arg1, T arg2, R arg3) {
return arg3;
}
#include <iostream>
#include <string>
using namespace std;
template<typename T, typename R> class Box {
public:
T length;
T breadth;
R height;
};
int main() {
Box<int, double> b;
}
27预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
27.1#define
用于定义常量,表达式,一般都是大写,字母与字母之间用
_
分割
- 语法:
#define 宏名 常量
,例如:#define PI 3.14
- 参数宏:就是宏定义表达式
#include <iostream>
using namespace std;
#define MIN(a,b) (a<b ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
cout <<"较小的值为:" << MIN(i, j) << endl;
return 0;
}
27.2条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译
#include <iostream>
using namespace std;
#define DEBUG
#define MIN(a,b) (((a)<(b)) ? a : b)
int main ()
{
int i, j;
i = 100;
j = 30;
#ifdef DEBUG
cerr <<"Trace: Inside main function" << endl;
#endif
#if 0
/* 这是注释部分 */
cout << MKSTR(HELLO C++) << endl;
#endif
cout <<"The minimum is " << MIN(i, j) << endl;
#ifdef DEBUG
cerr <<"Trace: Coming out of main function" << endl;
#endif
return 0;
}
27.3预定义宏
28信号处理
信号是由操作系统传给进程的中断,会提早终止一个程序。在 UNIX、LINUX、Mac OS X 或 Windows 系统上,可以通过按 Ctrl+C 产生中断。有些中断信号不能被程序捕获,但是下表所列信号可以在程序中捕获,并可以基于信号采取适当的动作。这些信号是定义在 C++ 头文件
<csignal>
中。
28.1 signal函数
C++ 信号处理库提供了 signal 函数,用来注册信号,但是需要
raise
或激活事件(例如SIGINT需要ctrl+c
实现)激活信号,如果不激活就不会执行信号处理函数。
- 语法格式:
signal(registered signal, 信号处理函数)
这个函数接收两个参数:第一个参数是要设置的信号的标识符,第二个参数是指向信号处理函数的指针。函数返回值是一个指向先前信号处理函数的指针。如果先前没有设置信号处理函数,则返回值为 SIG_DFL。如果先前设置的信号处理函数为 SIG_IGN,则返回值为 SIG_IGN。
常用的registered:
实例:
#include <iostream>
#include <csignal>
#include <unistd.h>
using namespace std;
void signalHandler( int signum )
{
cout << "Interrupt signal (" << signum << ") received.\n";
// 清理并关闭
// 终止程序
exit(signum);
}
int main ()
{
// 注册信号 SIGINT 和信号处理程序
signal(SIGINT, signalHandler);
while(1){
cout << "Going to sleep...." << endl;
sleep(1);
}
return 0;
}
28.2raise函数
您可以使用函数 raise() 激活信号,该函数带有一个整数信号编号作为参数,语法如下
- 语法:int raise(signal sig);
28.3Sleep函数
执行挂起一段时间,也就是等待一段时间在继续执行,用法:Sleep(时间),Sleep 括号里的时间,在 Windows 下是以毫秒为单位,而 Linux 是以秒为单位。Linux 用 #include <unistd.h> 和 sleep(),Windos 用 #include <windows.h> 和 Sleep()。
29STL
C++ STL(标准模板库)是一套功能强大的 C++ 模板类,提供了通用的模板类和函数,这些模板类和函数可以实现多种流行和常用的算法和数据结构,如向量、链表、队列、栈。C++ 标准模板库的核心包括以下三个组件:
30.常见的类
- SAFEARRAY
SafeArray也并不单独使用,而是将其再包装到VARIANT类型的变量中,然后才作为参数传送出去。