目录
前言:
C++的历史:
c++的领域
1.C++的关键字:
2.命名空间
2.1命名空间的定义:
1)命名空间的普通定义:
2)命名空间的嵌套定义:
3)命名空间相同的处理:
2.2命名空间的使用说明:
1)符号 : : 的使用
2)使用 using namespace 的全部内容引入
3)使用 using 的部分内容引入
3.C++的输入输出
4.缺省参数
4.1缺省参数的概念
4.2缺省参数的分类:
1)全缺省参数
2)半缺省参数
4.3缺省参数注意事项
1)缺省必须从左往右依次来缺省,不能间隔着缺省
2)缺省参数不能在函数声明和函数实现中同时出现
3)缺省值必须是常量或者变量
5.函数重载
5.1函数重载概念
1)参数类型不同
2)参数个数不同
3)参数顺序不同
5.2函数重载实现原理(了解)
6.引用:
6.1引用的概念
6.2引用的特性
6.3常引用
*临时变量
总结
6.4使用场景
6.5引用和指针的区别
6.6 引用和指针的区别
7.内敛函数
7.1概念
7.2内联函数的特性
8.auto关键字
8.1 auto的引入
8.2 auto简介
8.3 auto的使用
8.4 auto不能自动推导的场景
9.基于范围的for循环
9.1范围for语法:
9.2范围for的使用条件
10.指针的空值nullptr
前言:
一些c++发展的介绍和c++面向岗位的介绍
C++的历史:
c++的领域
我们要学习c++首先要知道c++学好之后能干什么?
1.操作系统以及大型系统软件开发
所有操作系统几乎都是C/C++写的,许多大型软件背后几乎都是C++写的,比如:
Photoshop、Office、JVM(Java虚拟机)等,究其原因还是性能高,可以直接操控硬件。
2.服务器端开发
后台开发:主要侧重于业务逻辑的处理,即对于前端请求后端给出对应的响应,现在主流采
用java,但内卷化比较严重,大厂可能会有C++后台开发,主要做一些基础组件,中间件、
缓存、分布式存储等。服务器端开发比后台开发跟广泛,包含后台开发,一般对实时性要求
比较高的,比如游戏服务器、流媒体服务器、网络通讯等都采用C++开发的
3.游戏开发
PC平台几乎所有的游戏都是C++写的,比如:魔兽世界、传奇、CSgo、等,市面上相当多的游戏引擎都是基于C++开发的,比如:Cocos2d、虚幻4、DirectX等。三维游戏领域计算量非常庞大,底层的数学全都是矩阵变换,想要画面精美、内容丰富、游戏实时性搞,这些高难度需求无疑只能选C++语言。
比较知名厂商:腾讯、网易、米哈游等。
4. 嵌入式和物联网领域
嵌入式:就是把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,能够控制这些
装置。比如:智能手环、摄像头、扫地机器人、智能音响等。
谈到嵌入式开发,大家最能想到的就是单片机开发(即在8位、16位或者32位单片机产品或者
裸机上进行的开发),嵌入式开发除了单片机开发以外,还包含在soc片上、系统层面、驱动
层面以及应用、中间件层面的开发。
常见的岗位有:嵌入式开发工程师、驱动开发工程师、系统开发工程师、Linux开发工程
师、固件开发工程师等。
5. 数字图像处理
数字图像处理中涉及到大量数学矩阵方面的运算,对CPU算力要求比较高,主要的图像处理
算法库和开源库等都是C/C++写的,比如:OpenCV、OpenGL等,大名鼎鼎的Photoshop就是C++写的。
6. 人工智能
一提到人工智能,大家首先想到的就是python,认为学习人工智能就要学习python,这个
是误区,python中库比较丰富,使用python可以快速搭建神经网络、填入参数导入数据就
可以开始训练模型了。但人工智能背后深度学习算法等核心还是用C++写的。
7. 分布式应用
后端架构要不断提高性能和并发能力才能应对大信息时代的来临。在分布式领域,好些分布式框架、文件系统、中间组件等都是C++开发的。对分布式计算影响极大的Hadoop生态的几个重量级组件:HDFS、zookeeper、HBase等,也都是基于Google用C++实现的GFS、Chubby、BigTable。
包括分布式计算框架MapReduce也是Google先用C++实现了一套,之后才有开源的java版本。
1.C++的关键字:
c++总共有63个关键字,其中包含c语言的32个(圈出来的)
这些关键字不需要特意去记,在我们日后写代码的过程中会慢慢用到并记住。
2.命名空间
命名空间是什么?
我们在学校学习c++的时候,一般都会在开头写上:using namespace std 这个就是一个命名空间
只不过老师很少讲这个是用来干什么的,这里我们就来详细的说一下:
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。
使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的
命名空间就是一个新的作用域,命名空间中所有的内容都会局限在该空间内
2.1命名空间的定义:
定义命名空间,需要使用到 namespace 关键字,后面根命名空间的名字,然后接一对 { } 即可
{ } 中包含的就是命名空间的成员
1)命名空间的普通定义:
//普通的命名空间
namespace A //A为空间的名称
{
//命名空间中的内容,既可以是变量,也可以是函数
int a;
int Sum(int x, int y)
{
return x + y;
}
}
2)命名空间的嵌套定义:
命名空间可以嵌套使用,(无限套娃)
// 命名空间可以嵌套
namespace A // 定义一个名为A的命名空间
{
int a;
int b;
namespace B // 嵌套定义另一个名为B的命名空间
{
int c;
int d;
}
}
3)命名空间相同的处理:
同一个工程里面,如果存在多个相同的命名空间,编译器会在最后将其整合为一个命名空间
// 定义一个A
namespace A
{
int a;
int Add(int x, int y) {
return x + y;
}
}
// 再定义一个A
namespace A
{
int Mul(int left, int right)
{
return left * right;
}
}
2.2命名空间的使用说明:
命名空间的使用分为三种:
- 符号 : : 在C++中叫做作用域限定符
- 用 using namespace 关键字将命名空间里面的全部内容引入
- 用 using 关键字将命名空间里面的部分内容引入
1)符号 : : 的使用
: : 叫做:作用域限定符,我们通过 命名空间::命名空间成员 来访问命名空间里面的内容
// 加命名空间名称及作用域限定符
namespace A
{
int a = 10;
float b;
}
int main()
{
A::b = 3.6;//引入命名空间b,并赋值
printf("%d\n", A::a); // 打印a
printf("%.2f\n", A::b); // 打印b
return 0;
}
结果:
2)使用 using namespace 的全部内容引入
// 使用 using namespace 命名空间名称引入
namespace A
{
int a;
float b;
}
using namespace A; // 将命名空间A的所有成员引入
int main()
{
a = 10; // 将命名空间中的成员a赋值为10
printf("%d\n", a); // 打印命名空间中的成员a
return 0;
}
3)使用 using 的部分内容引入
// 使用using将命名空间中的成员引入
namespace A
{
int a;
float b;
char c;
}
using A::a; // 将命名空间中的成员a引入
using A::b; // 再将b引入
int main()
{
a = 10; // 将命名空间中的成员a赋值为10
b = 3.69; // 将b赋值为5.55
printf("%d\n", a); // 打印成员a
printf("%.2f\n", b); // 打印成员b
return 0;
}
结果:
3.C++的输入输出
C语言的输入输出用到的符号是 printf 和 scanf 那C++用到的是什么呢?
我们先打一个hello world
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
结果:
解释:
- c语言的标准输入输出是 printf 和 scanf
- c++的是:cout(流插入) 和 cin(流提取),
- <<流插入的指令,>>流提取的指令
- 在使用 cout 和 cin 时,需要包含头文件 <iostream> 以及 std 标准命名空间
- endl 相当时换行的意思,相当于 ' \n '
我们在写C语言的输入输出时,需要加数据类型来进行控制,如整型:%d,字符:%c
而在C++的输入输出中,我们不用增加数据类型的控制,cout 和 cin 会自动识别
4.缺省参数
4.1缺省参数的概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。
在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
// a = 0 ,0就是a的默认值
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func(); // 没有传参时,使用参数的默认值(打印0)
Func(10); // 传参时,使用指定的实参(打印10)
return 0;
}
第一个Func输出默认值0,第二个Func输出结果时指定值10
4.2缺省参数的分类:
缺省参数分为:
- 全缺省
- 半缺省
1)全缺省参数
全缺省参数的所有参数都有默认值,如果没有手动传参,那么编译器会使用默认参数列表中的参数
注意:如果只传了部分参数,那么这个值会被从左到右匹配
2)半缺省参数
void Func1(int a, int b = 2, int c = 3)
{
cout << "a = "<< a << " b = " << b << " c = " << c << endl;
}
void Func2(int a, int b = 2, int c = 3)
{
cout << "a = " << a << " b = " << b << " c = " << c << endl;
}
其中Func1至少需要传一个参数,Func2至少需要传两个参数
4.3缺省参数注意事项
1)缺省必须从左往右依次来缺省,不能间隔着缺省
// 错误示例
void Func(int a, int b = 20, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
// 正确示例
void Func(int a, int b = 20, int c = 30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
2)缺省参数不能在函数声明和函数实现中同时出现
错误示例:
// Test.h(函数声明)
void Test(int a, int b, int c = 30);
// Test.c(函数定义)
void TestFunc(int a, int b, int c = 30) {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
-------------------------------------------
正确示例:
// Test.h(函数声明)
void Test(int a, int b, int c = 30);
// Test.c(函数定义)
void TestFunc(int a, int b, int c) {
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
如果 声明 和 定义 同时出现缺省的默认参数,编译器将无法确定到底该用哪个缺省数
一般把缺省的默认参数写在函数声明中
3)缺省值必须是常量或者变量
// 正确示例
int x = 30; //全局变量
void Func(int a, int b = 20, int c = x)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
5.函数重载
5.1函数重载概念
函数重载是指:在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。
重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处
函数重载的三种方法:
- 参数类型不同
- 参数个数不同
- 参数顺序不同
1)参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
2)参数个数不同
void Func(int a)
{
cout << "Func(int a)" << endl;
}
void Func(int a, int b)
{
cout << "Func(int a, int b)" << endl;
}
3)参数顺序不同
void Func(int a, char b)
{
cout << "Func(int a,char b)" << endl;
}
void Func(char b, int a)
{
cout << "Func(char b, int a)" << endl;
}
5.2函数重载实现原理(了解)
为什么C++可以做到重载,而C语言不行呢?
一个程序运行起来需要经历以下几个阶段:预处理、编译、汇编、链接
c语言中,在汇编阶段进行符号汇总时,一个函数汇总后的符号就是其函数名,所以当汇总时发现多个相同函数的函数符号时,编译器就会报错
在c++中,进行汇编阶段进行符号汇总时,对函数的名字修饰做了改动,函数汇总的符号不单单只是函数的函数名,而是通过其函数的类型,个数,顺序,等信息汇总层一个符号
这样,就算是函数名相同的函数,只要其参数的类型或参数的个数或参数的顺序不同,那么汇总出来的符号也就不同了。
6.引用:
6.1引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空 间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。
引用的使用:类型& 引用变量名(对像名)= 引用实体
注意:引用类型必须和引用实体是同种类型
6.2引用的特性
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,在不能引用其他的实体
1)引用在定义时必须初始化
错误用法:
int a = 10;
int& b;
b = a;
正确用法:
int a = 10;
int& b = a; // 定义时必须初始化
2)一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = a;
int& d = a;
b、c、d都是变量a的引用
3)引用一旦引用一个实体,在不能引用其他的实体
int a = 10;
int& b = a;
int c = 20;
b = c;
此时,b已经是a的引用了,b不能再引用其他实体,
这里b=c的意思是将c的值赋给b,也就是将b和a的内容改为了20
6.3常引用
引用类型必须和引用实体是同种类型的。
但如果一个普通引用去引用被const所修饰的类型,那么是不被允许的
这是因为我们对引用别名是有原则的:对原变量的引用,权限只能缩小,不能放大
1)权限不变的情况:
2)权限缩小的情况:
a变量是可读可写的,它的权限是最大的,我们可以控制b的权限,使得b的权限降低
可以看到缩小b的权限后,b不能被改变了
3)权限放大
上面代码中:a 变量用 const 修饰,说明变量不能被修改,是只读的,那么我们定义别名的时候,也必须拿 const 修饰
*临时变量
还有一种情况也必须用 const,下面代码中,d 是 double 类型,而 a 和 b 都是 int 类型,但是如果想要为 d 取一个别名,必须加上引用,因为把 double 类型赋值给 int 类型会涉及到隐式类型转换,那么 d 在此时就是一个临时变量,临时变量具有常性是只读的,所以要加 const
这里的d是一个临时变量,具有常性,必须要用const进行修饰
总结
- const引用是为了保护实参,避免被修改,
- 函数传参的时候,不想原变量被改变,这个参数可以使用const引用传参
6.4使用场景
1)做参数:
先看一段代码:
//C语言的传址调用
void Swap1(int* p1, int* p2) {
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
//C++的引用调用
void Swap2(int& rx, int& ry) {
int temp = rx;
rx = ry;
ry = temp;
}
int main()
{
int x = 3, y = 6;
Swap1(&x, &y); // C传参
Swap2(x, y); // C++传参
return 0;
}
这里引用就相当于再swap2的函数定义是,给参数用一个引用(起一个别名),引用的改变,原来的数也会改变
能明显发现,使用引用的方法,来替换指针的使用,能更方便一点
2)做返回值
下面这段代码中,我们再func里面定义了一个变量,出了作用域,n会销毁,编译器这时会用一个临时变量来记录n的值,编译器创建的临时变量时被const所修饰的,最会将n的值赋值给ret
int func()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = func();
cout << ret << endl;
ret = Cout();
cout << ret << endl;
return 0;
}
但如果我们返回的数据必须是被 static 修饰,或者是 动态开辟 的,再或者是 全局变量 等一些不会随着函数调用的结束而被销毁的数据,就可以使用引用做返回值
int& func()
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = func();
cout << ret << endl;
ret = Cout();
cout << ret << endl;
return 0;
}
那么用引用返回有什么好处呢?
用引用返回,就会避免编译器产生临时变量,如果你返回的是一个结构一个类,那么这个临时变量会变得很大,会降低代码的效率
总结:
- 如果函数返回时,出了函数作用域,返回对象未销毁,则可以使用引用返回;
- 如果已经还给系统了,则必须使用传值返回。
6.5引用和指针的区别
在语法上,引用就是一个别名,没有独立空间,和其引用实体公用同一块空间
而指针变量时开辟一块空间,存储变量的地址
可以看到,a和它的引用b的地址是一样的
但其实,在底层的实现上实际是有开辟新空间的,因为引用的底层逻辑时按照指针方式来实现的
来看看 汇编代码,就可以看的很明白
6.6 引用和指针的区别
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
7.内敛函数
在程序中,大量的重复的建立函数栈帧会造成很大的性能开销
在 C 语言可以用宏函数来代替函数,使之不会开辟栈帧,
虽然宏的优点多,但也有不少的缺点(比如使用起来麻烦,需要注意的细节多),
这时 内联函数 就可以针对这种场景解决问题 (内联函数对标宏函数)。
7.1概念
以 inline修饰 的函数叫做 内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
没有使用内敛函数之前,汇编的时候,需要进行函数调用,传递的是函数的地址
用了内联函数之后,可以看到汇编的时候是直接展开的
7.2内联函数的特性
- inline 是一种 以空间换时间 的做法,在编译阶段,会用函数体替换函数调用,省去调用函数额开销。所以 代码很长 或者 有循环 或者 有递归 的函数不适宜使用作为内联函数。
- inline 对于编译器而言只是一个建议,编译器会自动优化,如果定义为 inline 的函数体内 有循环 或者 有递归 等等,编译器优化时会忽略掉内联。
- inline 不建议声明和定义分离,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,链接就会找不到。
8.auto关键字
8.1 auto的引入
auto是自动识别类型的关键字,经常使用在:
- 类型难拼写
- 含义不明确导致容易出错
#include <string>
#include <map>
//auto关键字
int main()
{
std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange", "橙子" },{"pear","梨"} };
std::map<std::string, std::string>::iterator it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}
对于这段代码,大家不用在意它是什么意思,
其中 std::map<std::string, std::string>::iterator 这个是一个类型,但是他太长了,容易写错,而且麻烦。
这时,我们大多数人会想到用 typedef 给他区别名,比如:
#include <string>
#include <map>
typedef std::map<std::string, std::string> Map;
int main()
{
Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
Map::iterator it = m.begin();
while (it != m.end())
{
//....
}
return 0;
}
使用typedef给类型取别名确实可以简化代码,但是typedef有会遇到新的问题:
typedef char* pstring;
int main()
{
const pstring p1; // 编译成功还是失败?
const pstring* p2; // 编译成功还是失败?
return 0;
}
8.2 auto简介
在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。
注意:
- 使用 auto 定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导 auto 的实际类型。
- 因此 auto 并非是一种 “类型” 的声明,而是一个类型声明时的 “占位符”,编译器在编译期会将 auto 替换为变量实际的类型。
8.3 auto的使用
1)auto与指针的引用结合起来使用
用 auto 声明指针类型时,用 auto 和 auto* 没有任何区别,
但用auto声明引用时,必须加&,否则城建的知识与实体类型相同的不同变量
2)在同一行定义多个变量
int main()
{
auto a = 1, b = 2; // 正常通过
auto c = 3, d = 3.14; // 编译器报错:“auto”必须始终推导为同一类型
return 0;
}
8.4 auto不能自动推导的场景
- auto 不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导 void TestAuto(auto a) { //.... }
- auto 不能直接用来声明数组
void TestAuto() { int a[] = {1, 2, 3}; auto b[] = {4,5,6}; // 此处编译失败,错误写法 }
- 为了避免与 C++98 中的 auto 发生混淆,C++11 只保留了 auto 作为类型指示符的用法
- auto 在实际中最常见的优势用法就是跟 C++11 提供的新式 for 循环,还有 lambda 表达式等进行配合使用。
9.基于范围的for循环
9.1范围for语法:
我们遍历数组的方法如下:
int main()
{
int arr[] = { 1,2,3,4,5 };
// 打印数组中的所有元素
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); ++i)
{
cout << arr[i] << " ";
}
cout << endl;
return 0;
}
基于范围的for循环引入了auto,会自动判断结束标志
for 循环后的括号由冒号 : 分为两部分:
第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围
和普通循环类似,可以用 continue 和 break 来进行控制
9.2范围for的使用条件
1)for循环迭代的范围必须是确定的
- 对于数组而言,就是数组中第一个元素和最后一个元素的范围;
- 对于类而言,应该提供 begin和end的方法,begin和end就是for循环迭代的范围。
以下代码就是有问题的代码,因为它for的范围不能确定,函数调用拿到的时array的首元素地址
void TestFor(int array[])
{
for(auto& e : array)
cout<< e <<endl;
}
2)迭代的对象要实现++和==的操作
(需要其他知识的补充才能理解到位,放到以后会讲)
10.指针的空值nullptr
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现 不可预料的错误,比如未初始化的指针。
如果一个指针没有合法的指向,我们基本都是按照如下 方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
}
NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
void f(int)
{
cout << "f(int)" << endl;
}
void f(int*)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数,
但是由于NULL被定义成0,因此与程序的初衷相悖。
注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
c++第一篇就到这里学完了,可以看到c++相比c有很多补充,也有更多的细节需要记,
前路漫漫还得继续努力了!