C++世界的大门——基础知识总结
目录
- 一. C++简介
- 二. C++的第一个程序
- 三. 命名空间
- 1. namespace的定义
- 2. 命名空间的使用
- 四. C++的输入与输出
- 五.缺省参数
- 六. 函数重载
- 七. 引用
- 1. 概念和定义
- 2. 引用的特性
- 3. 引用的使用
- 4. const引用
- 5. 引用和指针的关系
- 八. inline
- 九. nullptr
一. C++简介
C++ 是一种高级编程语言,由 Bjarne Stroustrup 在贝尔实验室开发。它是 C 语言的超集,支持面向过程、面向对象和泛型编程。C++ 以高性能和控制硬件资源的能力而闻名,广泛应用于游戏开发、操作系统、浏览器、数据库等系统级软件领域。
C++在工作领域中的应⽤ C++的应用领域服务器、游戏(引擎)、机器学习引擎、音视频处理、嵌入式软件、电信设备、金融应用、基础库、操作系统、编译器、基础架构、基础工具、硬件交互等很多方面都有。
- 大型系统软件开发 如编译器、数据库、操作系统、浏览器等等
- 音视频处理 常见的音视频开源库和方案有FFmpeg、WebRTC、Mediasoup、ijkplayer,音视频开发最主要的技术栈就是C++。
- PC客户端开发 ⼀般是开发Windows上的桌面软件,比如WPS之类的,技术栈的话⼀般是C++和QT,QT 是⼀个跨平台的 C++图形用户界面(Graphical User Interface,GUI)程序。
- 服务端开发 各种⼤型应⽤⽹络连接的高并发后台服务。这块Java也⽐较多,C++主要用于⼀些对性能要求比较高的地方。如:游戏服务、流媒体服务、量化高频交易服务等
- 游戏引擎开发 很多游戏引擎就都是使用C++开发的,游戏开发要掌握C++基础和数据结构,学习图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习UE4、Cocos2d-x等开源引擎实现
- 嵌入式开发 嵌入式把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,通过软件能够控制这些装置。比如:智能手环、摄像头、扫地机器人、智能音响、门禁系统、车载系统等等,粗略⼀点,嵌入式开发主要分为嵌入式应用和嵌入式驱动开发。
- 机器学习引擎 机器学习底层的很多算法都是用C++实现的,上层用python封装起来。如果你只想准备数据训练模型,那么学会Python基本上就够了,如果你想做机器学习系统的开发,那么需要学会C++。
- 测试开发/测试 每个公司研发团队,有研发就有测试,测试主要分为测试开发和功能测试,测试开发⼀般是使用⼀些测试工具(selenium、Jmeter等),设计测试用例,然后写⼀些脚本进行自动化测试,性能测试等,有些还需要自行开发⼀些测试用具。功能测试主要是根据产品的功能,设计测试用例,然后手动的方式进行测试。
二. C++的第一个程序
C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以运行,C++中需要把定义文件
代码后缀改为.cpp。
// test.cpp
#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
当然C++也有⼀套自己的输⼊输出,严格说C++版本的hello world应该是这样写的。
#include<iostream>
using namespace std;int main()
{
cout << "hello world\n" << endl;
return 0;
}
- #include<iostream>: 包含标准输入输出库的头文件,使我们可以使用 cout 和 cin。
- using namespace std: std 是标准库的命名空间。cout 和 endl 等都定义在其中。这行代码让我们可以直接使用它们,而不必写成 std::cout。
- int main() {…}: 程序的主函数,所有 C++ 程序都必须有且只有一个 main 函数。程序从这里开始执行。
- cout << … << endl: cout 是标准输出流对象,<< 是流插入运算符,用于将内容发送到控制台。endl 用于插入一个换行符并刷新缓冲区。
- return 0: 向操作系统返回一个退出码,0 通常表示成功。
三. 命名空间
在C/C++中,变量、函数和后面要学到的类都是⼤量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
c语⾔项目类似下面程序这样的命名冲突是普遍存在的问题,C++引入namespace
就是为了更好的解决这样的问题
1. namespace的定义
- 定义命名空间,需要使用到
namespace
关键字,后面跟命名空间的名字,然后接⼀对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。 namespace
本质是定义出⼀个域,这个域跟全局域各自独立,不同的域可以定义同名变量。- C++中域有函数 局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
namespace
只能定义在全局,还可以嵌套定义。多文件中定义的同名namespace会认为是一个namespace,不会冲突。C++标准库都放在一个叫std(standard)的命名空间中。
2. 命名空间的使用
编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以我们要使用命名空间中定义的变量/函数,有三种方式:
- 指定命名空间访问。展开需要的部分
using std::cout
;using std::endl
; - using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。
- 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常学习程序为了方便推荐使用。
namespace s
{int rand = 20;int Add(int a ,int b){return a+b;}
}
int a = 2;
int main()
{int a = 1;printf("%d\n",a);//输出1printf("%d\n",::a);//输出2, ::域作用限定符printf("%d\n",s::rand);//输出20printf("%d\n",s::Add(1,2));//输出3return 0;
}
四. C++的输入与输出
- <iostream> 是 Input Output Stream 的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。
- std::cin 是 istream 类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。
- std::cout 是 ostream 类的对象,它主要面向窄字符的标准输出流。
- std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换行字符加刷新缓冲区。
<<
是流插入运算符,>>
是流提取运算符。(C语⾔还用这两个运算符做位运算左移/右移)- 使用C++输⼊输出更方便,不需要像printf/scanf输⼊输出时那样,需要手动指定格式,C++的输⼊输出可以自动识别变量类型,其实最重要的是C++的流能更好的⽀持自定义类型对象的输入输出。
- cout/cin/endl 都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。
#include<iostream>
int main()
{int a = 1;double b = 1.1;char c = 'a';printf("%d\n", a);printf("%.1f\n", b);printf("%c\n", c);std::cout << a << std::endl;std::cout << b << std::endl;std::cout << c << std::endl;std::cin >> a;std::cout << a << std::endl;return 0;
}
#include<iostream>
using namespace std;int main()
{int a = 1;double b = 1.1;char c = 'a';printf("%d\n", a);printf("%.1f\n", b);printf("%c\n", c);cout << a << endl;cout << b << endl;cout << c << endl;cin >> a;cout << a << endl;return 0;
}
#include<iostream>
using namespace std;
int main()
{//在io需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码//可以提⾼C++IO效率ios_base::sync_with_stdio(false);cin.tie(nullptr);cout.tie(nullptr);return 0;
}
五.缺省参数
- 缺省参数是在声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。
全缺省就是全部形参给缺省值
,半缺省就是部分形参给缺省值
。C++规定半缺省参数必须 从右往左依次连续缺省,不能间隔跳跃给缺省值。- 带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
- 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
#include <iostream>using namespace std;//全缺省
void Fun1(int a = 10,int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;cout << endl;
}//半缺省
void Fun2(int a ,int b = 20, int c = 30)
{cout << "a = " << a << endl;cout << "b = " << b << endl;cout << "c = " << c << endl;cout << endl;
}
int main()
{Fun1();//输出 10 20 30Fun1(1);//输出 1 20 30Fun1(1,2);//输出 1 2 30Fun1(1,2,3);//输出 1 2 3Fun2(1);//输出 1 20 30Fun2(1,2);//输出 1 2 30Fun2(1,2,3);//输出 1 2 3return 0;
}
六. 函数重载
C++⽀持在同⼀作用域中出现同名函数
,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态行为,使用更灵活。C语言是不支持同⼀作用域中出现同名函数的。
#include<iostream>
using namespace std;//参数类型不同
void Add(int a, int b)
{cout << a + b << endl;
}void Add(double a, double b)
{cout << a + b << endl;
}//参数个数不同
void fun()
{cout << "fun()" << endl;
}
void fun(int a)
{cout << "fun(int a)" << endl;
}//参数类型的顺序不同
void fun(int a, double b)
{cout << "fun(int ,double)" << endl;
}
void fun(double a, int b)
{cout << "fun(double ,int)" << endl;
}int main()
{//参数类型不同Add(1, 2);Add(1.1, 2.1);//参数个数不同fun();fun(1);//参数类型的顺序不同fun(1, 2.0);fun(2.1, 1);return 0;
}
下面两个函数构成重载
但是调用时会报错,Fun1和Fun2存在歧义
#include<iostream>
using namespace std;void Fun1()
{cout << "Fun1()"<< endl;
}void Fun1(int a = 10)
{cout << "Fun1(a)"<< endl;}
int main()
{Fun1();Fun1(1);return 0;
}
七. 引用
1. 概念和定义
引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共同同⼀块内存空间。
类型& 引用别名 = 引⽤对象
C++中为了避免引⼊太多的运算符,会复用C语⾔的⼀些符号,⽐如前面的<< 和 >>,这里引用也和取地址使用了同⼀个符号&,大家注意使用方法角度区分就可以。
#include<iostream>
using namespace std;
int main()
{int a = 0;int b = 1;//引用:c是a的别名int& c = a;//d是b的别名int& d = b;//++d,b也会加1++d;//这里取地址我们看到是⼀样的cout << &a << endl;cout << &c << endl;cout << a << endl;//输出 0cout << c << endl;//输出 0cout << b << endl;//输出 2cout << d << endl;//输出 2return 0;
}
2. 引用的特性
- 引用在定义时必须初始化
- ⼀个变量可以有多个引用
- 引用⼀旦引用⼀个实体,再不能引用其他实体
#include<iostream>
using namespace std;
int main()
{int a = 10;// 编译报错:“ra”: 必须初始化引用//int& ra;int& b = a;int c = 20;// 这里并非让b引用c,因为C++引用不能改变指向,// 这里是一个赋值b = c;cout << &a << endl;cout << &b << endl;cout << &c << endl;return 0;
}
3. 引用的使用
- 引用在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
- 引用传参跟指针传参功能是类似的,引用传参相对更方便⼀些。
- 引用和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引用跟其他语言的引用(如Java)是有很大的区别的,除了用法,最大的点,C++引用定义后不能改变指向,Java的引用可以改变指向。
void Swap(int& rx, int& ry)
{int tmp = rx;rx = ry;ry = tmp;
}int main()
{int x = 0, y = 1;cout << x <<" " << y << endl;//输出 0 1Swap(x, y);cout << x << " " << y << endl;//输出 1 0return 0;
}
4. const引用
- 可以引用⼀个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。
- 不需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场景下a * 3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产生临时对象存储中间值,也就是时,rb和rd引用的都是临时对象,而C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。所谓临时对象就是编译器需要一个空间暂存表达式的求值结果临时创建的一个未命名的对象,C++中把这个未命名的对象叫做临时对象。
int main()
{const int a = 10;// 编译报错:“初始化”无法从“const int”转换为“int &”// 这里的引用是对a访问权限的放大//int& ra = a;// 这样才可以const int& ra = a;// 编译报错:“ra”: 不能给常量赋值//ra++;// 这里的引用是对b访问权限的缩小int b = 20;const int& rb = b;// 编译报错:“rb”: 不能给常量赋值//rb++;return 0;
}
int main()
{int a = 10;const int& ra = 30;//编译报错: “初始化”无法从“int”转换为“int &”//int& rb = a * 3;const int& rb = a * 3;double d = 12.34;//编译报错:“初始化”无法从“double”转换为“int &”//int& rd = d;const int& rd = d;return 0;
}
5. 引用和指针的关系
- 引用在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
- 引用在初始化时引用⼀个对象后,就不能再引用其他对象,而指针可以在不断地改变指向对象。
- 引用可以直接访问指向对象,指针需要解引用才是访问指向对象。
- sizeof中含义不同,引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8个字节)
- 指针很容易出现空指针和野指针的问题,引用很少出现,引用使用起来相对更安全⼀些。
八. inline
用inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联函数就需要建立栈帧了,就可以提高效率。
- inline对于编译器而言只是⼀个建议,也就是说,加了inline编译器也可以选择在调用的地方不展开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
- C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不方便调试,C++设计了inline目的就是替代C的宏函数。
- inline不能声明和定义分离到两个文件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
inline int Add(int x, int y)
{int ret = x + y;ret += 1;ret += 1;ret += 1;return ret;
}
int main()
{//可以通过汇编观察程序是否展开//有call Add语句就是没有展开,没有就是展开了int ret = Add(1, 2);cout << Add(1, 2) * 5 << endl;return 0;
}
九. nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中。
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif
- nullptr 是 C++11 引入的空指针字面量。它是一个关键字,用于表示指针不指向任何有效的内存地址。简单来说,nullptr 就是一个类型安全的、明确的“空”指针值。
- C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量。
- C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字面量,它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型。
#include <iostream>
using namespace std;int main() {int* ptr1 = nullptr; // 推荐:清晰表明这是一个空指针int* ptr2 = 0; // 不推荐:看起来像是个整数// int* ptr3 = NULL; // 不推荐:在C++中已过时if (ptr1 == nullptr) {cout << "ptr1 是空指针" << endl;}// 检查指针是否有效是良好的编程习惯if (ptr1) { // 如果 ptr1 不是 nullptr,条件为真cout << "ptr1 指向某个值: " << *ptr1 << endl;} else {cout << "ptr1 是空的,不能解引用!" << endl;}return 0;
}
- 在C++中所有需要表示空指针的地方,都使用 nullptr。
- 避免使用 NULL 和 0 来表示空指针。
- 在使用指针之前,总是检查它是否为 nullptr,这是一种防御性编程的好习惯,可以防止程序崩溃。
总结
C++的学习是一场令人兴奋的冒险,而牢固的基础是这场冒险中最可靠的装备。C++的魅力远不止于此。在这份基础之上,你将迈向更核心的领域:面向对象编程(类与对象、封装、继承、多态)、内存管理(指针、引用) 以及强大的标准模板库(STL)。每一个概念都将为你打开新世界的大门。不要畏惧,从今天开始,多写代码,多调试,多思考。尝试用这些基础知识去解决一些小问题,比如做一个简单的计算器、一个成绩管理系统。实践是检验真理的唯一标准,也是学习编程最快路径。
路漫漫其修远兮,吾将上下而求索。C++的世界,等你来探索!。最后感谢大家的点赞、关注、评论和收藏。
前期回顾
排序(Sort)方法详解(冒泡、插入、希尔、选择、堆、快速、归并)
《数据结构》——二叉树从概念到代码的详解
力扣(LeetCode) ——965. 单值二叉树(C语言)