大家好,我是小卡皮巴拉
文章目录
目录
[1.1 C++的发展历史](#1.1 C++的发展历史)
[1.2 C++的重要性](#1.2 C++的重要性)
[1.3 C++在工作领域中的应用](#1.3 C++在工作领域中的应用)
[2.1 C语言版本](#2.1 C语言版本)
[2.2 C++版本](#2.2 C++版本)
[3.1 命名空间的定义](#3.1 命名空间的定义)
[3.2 命名空间的作用](#3.2 命名空间的作用)
[3.3 命名空间的使用](#3.3 命名空间的使用)
[使用 using 指令](#使用 using 指令)
[使用 using 声明](#使用 using 声明)
[标准输出流 cout](#标准输出流 cout)
[标准输入流 cin](#标准输入流 cin)
[const 引用的特性](#const 引用的特性)
[兄弟们共勉 !!!](#兄弟们共勉 !!!)
每篇前言
博客主页:小卡皮巴拉
咱的口号:🌹小比特,大梦想🌹
作者请求:由于博主水平有限,难免会有错误和不准之处,我也非常渴望知道这些错误,恳请大佬们批评斧正。
引言
C++ 是一门功能强大、应用广泛的编程语言。对于刚接触编程的人来说,学习 C++ 是一次极具意义的体验。它有着独特的语法和逻辑,从基础的程序结构到复杂的数据处理,每一步都充满挑战与乐趣。现在,让我们开始初步认识 C++,开启编程学习的新征程。
一.什么是C++
1.1 C++的发展历史
C++ 由本贾尼・斯特劳斯特卢普于 20 世纪 70 年代末在贝尔实验室开发,最初是 "C with Classes"。1983 年更名为 C++,之后不断加入新特性。1998 年 C++98 标准发布,标志其成熟。2003 年 C++03 标准对其修正完善。2011 年 C++11 标准带来自动类型推导、智能指针等重大改进,后续 C++14、C++17、C++20 等标准持续更新,为满足现代软件开发需求引入更多新特性。C++ 因高效、灵活和功能强大,广泛用于系统编程、游戏开发等众多领域,对其他编程语言也有深远影响。
1.2 C++的重要性
TIOBE排行榜是根据互联网上有经验的程序员、课程和第三方厂商的数量,并使用搜索引擎(如 Google、Bing、Yahoo!)以及Wikipedia、Amazon、YouTube和Baidu(百度)统计出排名数据,只是反映某个编程语言的热门程度,并不能说明一门编程语言好不好,或者一门语言所编写的代码数量多少。
2024年6月TIOBE发布的编程语言排行榜
由此可见,C++是当下比较热门的编程语言之一。
1.3 C++在工作领域中的应用
C++的应用领域服务器端、游戏(引擎)、机器学习引擎、音视频处理、嵌入式软件、电信设备、金融应用、基础库、操作系统、编译器、基础架构、基础工具、硬件交互等很多方面都有。
C++在工作领域中的主要应用如下:
-
大型系统软件开发。如编译器、数据库、操作系统、浏览器等等。
-
音视频处理。常见的音视频开源库和方案有FFmpeg、WebRTC、Mediasoup、ijkplayer,音视频 开发最主要的技术栈就是C++。
-
PC客户端开发。一般是开发Windows上的桌面软件,比如WPS之类的,技术栈的话一般是C++和 QT,QT是一个跨平台的C++图形用户界面(GraphicalUserInterface,GUI)程序。
-
服务端开发。各种大型应用网络连接的高并发后台服务。这块Java也比较多,C++主要用于⼀些对 性能要求比较高的地方。如:游戏服务、流媒体服务、量化高频交易服务等
-
游戏引擎开发。很多游戏引擎就都是使用C++开发的,游戏开发要掌握C++基础和数据结构,学习 图形学知识,掌握游戏引擎和框架,了解引擎实现,引擎源代码可以学习UE4、Cocos2d-x等开源 引擎实现
-
嵌入式开发。嵌入式把具有计算能力的主控板嵌入到机器装置或者电子装置的内部,通过软件能够 控制这些装置。比如:智能手环、摄像头、扫地机器人、智能音响、门禁系统、车载系统等等,粗 略⼀点,嵌入式开发主要分为嵌入式应用和嵌入式驱动开发。
-
机器学习引擎。机器学习底层的很多算法都是用C++实现的,上层用python封装起来。如果你只想 准备数据训练模型,那么学会Python基本上就够了,如果你想做机器学习系统的开发,那么需要学 会C++。
-
测试开发/测试。每个公司研发团队,有研发就有测试,测试主要分为测试开发和功能测试,测试 开发一般是使用一些测试⼯具(selenium、Jmeter等),设计测试用例,然后写一些脚本进行自动化 测试,性能测试等,有些还需要自行开发一些测试用具。功能测试主要是根据产品的功能,设计测 试用例,然后手动的方式进行测试。
二.第一个C++程序
C++兼容C语言绝大多数的语法,所以C语言实现的helloworld依旧可以运行,C++中需要把定义文件代码后缀改为.cpp,vs编译器看到是.cpp就会调用C++编译器编译,linux下要用g++编译,不再是gcc
2.1 C语言版本
C++兼容C语言绝大多数的语法,所以C语言实现的hello world依旧可以在当前文件下运行
2.2 C++版本
但是C++有着自己的一套输入输出,下面是C++版本的hello world
这里的 std cout 等我们都看不懂,没关系,下面我们会依次讲解。
三.命名空间
在 C++ 中,命名空间是一种用于组织和管理代码的机制,它可以帮助避免命名冲突,提高代码的可读性和可维护性。以下是关于 C++ 命名空间的详细讲解:
为什么C++中要使用命名空间?
在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。
3.1 命名空间的定义
命名空间的定义使用关键字namespace ,后面跟着命名空间的名字,再加上一对花括号,在花括号内可以声明各种标识符,如变量、函数、类等。例如:
cpp
namespace MyNamespace
{
int myVariable;
void myFunction()
{
// 函数体
}
class MyClass
{
// 类定义
};
}
namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量
C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响 编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
namespace只能定义在全局,当然他还可以嵌套定义。
项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。
C++标准库都放在⼀个叫std(standard)的命名空间中
3.2 命名空间的作用
避免命名冲突 :在大型项目中,不同的模块或库可能会定义相同名称的变量、函数或类,这会导致命名冲突。通过将代码放在不同的命名空间中,可以有效地隔离这些标识符,避免冲突。 例如,两个不同的库都定义了一个名为 print
的函数,使用命名空间可以将它们区分开来:
cpp
namespace Library1
{
void print()
{
std::cout << "This is print in Library1" << std::endl;
}
}
namespace Library2
{
void print()
{
std::cout << "This is print in Library2" << std::endl;
}
}
组织代码结构:命名空间可以按照功能、模块或团队等方式对代码进行逻辑分组,使代码结构更加清晰。
3.3 命名空间的使用
使用作用域解析运算符
**当需要访问命名空间中的标识符时,可以使用作用域解析运算符 ::。**例如,要访问上述
MyNamespace
中的变量和函数,可以这样写:
cppint main() { MyNamespace::myVariable = 10; MyNamespace::myFunction(); return 0; }
使用
using
指令**为了避免每次都使用作用域解析运算符,可以使用 using 指令将整个命名空间引入当前作用域。**例如:
cppusing namespace MyNamespace; int main() { myVariable = 10; myFunction(); return 0; }
但是,过度使用 using 指令可能会导致命名冲突,尤其是在包含多个不同命名空间的情况下。因此,在实际使用中需要谨慎。
使用
using
声明using 声明可以将命名空间中的特定标识符引入当前作用域,这样就可以直接使用该标识符,而无需使用作用域解析运算符。例如:
cppusing MyNamespace::myVariable; using MyNamespace::myFunction; int main() { myVariable = 10; myFunction(); return 0; }
嵌套命名空间
**命名空间可以嵌套定义,即在一个命名空间内部可以再定义其他命名空间。**例如:
cpp//命名空间支持嵌套定义 namespace bit { //鹏哥 namespace pg { int rand = 1; int Add(int left, int right) { return left + right; } } //杭哥 namespace hg { int rand = 2; int Add(int left, int right) { return (left + right) * 10; } } }
要访问嵌套命名空间中的标识符,可以使用多级作用域解析运算符,如
OuterNamespace::InnerNamespace::innerVariable
。
综上所述,我们要使用命名空间中定义的变量/函数,有三种⽅式:
• 指定命名空间访问,项目中推荐这种方式。
• using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。
• 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。
四.C++的输入和输出
C++ 的输入输出是通过标准库中的 iostream 库来实现的,主要涉及到cin
、cout
、cerr
、clog
等对象以及一些相关的操作符和函数。
包含头文件
使用 C++ 输入输出功能,首先需要包含<iostream>头文件。这个头文件包含了输入输出流的相关定义和声明,是使用cin
、cout
等对象的基础。
标准输出流
cout
基本用法 :cout是 C++ 中用于标准输出的对象,通常用于向控制台输出数据。使用
cout
时,需要配合<<操作符,将数据插入到输出流中。
cpp#include <iostream> int main() { int num = 10; std::cout << "The value of num is: " << num << std::endl; return 0; }
在上述代码中,<<操作符将字符串常量和变量num依次插入到cout输出流中,最终输出到控制台。
endl是一个特殊的操纵符,它不仅会输出一个换行符,还会刷新输出缓冲区,确保输出立即显示在控制台上。
标准输入流
cin
基本用法 :cin是用于标准输入的对象,它与
cout
相对应,用于从控制台读取用户输入的数据。使用cin
时,需要配合>>操作符,将输入流中的数据提取到变量中。例如:
cpp#include <iostream> int main() { int num; std::cout << "请输入一个整数: "; std::cin >> num; std::cout << "你输入的整数是: " << num << std::endl; return 0; }
在上述代码中,
cin >> num
会等待用户在控制台输入一个整数,并将其存储到变量num
中。
C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的,这个以后会讲到),其实最重要的是C++的流能更好的支持自定义类型对象的输入输出。
五.缺省参数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参 则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省和半缺省参数。(有些地方把 缺省参数也叫默认参数)
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左 依次连续缺省,不能间隔跳跃给缺省值。
语法形式
在函数声明或定义中,可以为参数指定默认值。指定默认值的方式是在参数列表中使用赋值运算符
=
为参数赋初值。例如:
cppvoid printMessage(const std::string& message = "Hello, World!");
在上述函数声明中,
message
参数有一个默认值"Hello, World!"。
使用规则
默认值的设定顺序 :**当函数有多个参数时,一旦某个参数开始使用默认值,那么它后面的所有参数都必须有默认值。**例如:
cppvoid func(int a, int b = 10, int c = 20); // 正确 void func(int a = 5, int b, int c = 20); // 错误,b没有默认值但在a之后
默认值的作用域 :缺省参数的默认值在函数声明和定义中只能出现一次。通常建议在函数声明中指定默认值,这样在包含该函数声明的多个源文件中都能使用相同的默认值,而函数定义中不再重复指定默认值。例如:
cpp// func.h void func(int a, int b = 10); // func.cpp #include "func.h" void func(int a, int b) { // 函数体 }
调用函数时的参数匹配 :在调用具有缺省参数的函数时,可以根据实际需要提供部分或全部参数。如果提供了参数,则使用提供的值;如果没有提供,则使用默认值。例如:
cppprintMessage(); // 使用默认值 "Hello, World!" printMessage("Goodbye!"); // 使用提供的值 "Goodbye!"
六.函数重载
**C++ 中的函数重载是指在同一作用域内,可以定义多个同名函数,但这些函数的参数列表必须不同,包括参数的类型、个数或顺序。**函数重载是 C++ 多态性的一种体现,它允许程序员使用相同的函数名来执行相似但参数不同的操作,从而提高了代码的可读性和可维护性。
函数重载的定义
**当多个函数具有相同的名字,但参数列表不同时,它们就构成了函数重载。**例如:
cpp
int add(int num1, int num2);
double add(double num1, double num2);
上述代码中定义了两个名为add
的函数,一个接受两个整数参数,另一个接受两个双精度浮点数参数。
函数重载有三种情况:
参数类型不同
cppint 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; }
参数个数不同
cppvoid f() { cout << "f()" << endl; } void f(int a) { cout << "f(int a)" << endl; }
参数类型顺序不同
cppvoid f(int a, char b) { cout << "f(int a,char b)" << endl; } void f(char b, int a) { cout << "f(char b, int a)" << endl; }
重载函数的匹配规则
精确匹配:当调用函数时,编译器首先尝试寻找与传入参数类型完全匹配的重载函数。例如:
cppint result1 = add(5, 10); // 调用 int add(int num1, int num2); double result2 = add(3.14, 2.71); // 调用 double add(double num1, double num2);
隐式类型转换匹配:如果没有找到精确匹配的函数,编译器会尝试进行隐式类型转换,以找到合适的重载函数。例如:
cppint num = 5; double result = add(num, 3.14); // int类型的num会隐式转换为double类型,调用double add(double num1, double num2);
匹配失败:如果经过隐式类型转换后仍然找不到合适的重载函数,编译器会报错。例如:
函数重载的注意事项
仅返回值类型不同不构成重载 :重载函数必须在参数列表上有所不同,仅返回值类型不同是不足以构成函数重载的。例如,下面的代码是错误的:
cppint add(int num1, int num2); double add(int num1, int num2); // 错误,仅返回值类型不同,参数列表相同
参数列表的差异必须明确 :函数重载的参数列表差异要足够明显,以便编译器能够在调用时准确地选择合适的重载函数。如果参数列表的差异不明确,可能会导致编译错误或意外的结果。
cpp#include <iostream> // 错误的函数重载示例:参数列表差异不明确 int add(int num1, int num2, int num3 = 0) { return num1 + num2 + num3; } int add(int num1, int num2 = 0, int num3 = 0) { return num1 + num2 + num3; } int main() { int result1 = add(5, 10); // 这里编译器无法确定调用哪个重载函数,会导致编译错误 int result2 = add(3, 7, 2); int result3 = add(4, 6); std::cout << "result1: " << result1 << std::endl; std::cout << "result2: " << result2 << std::endl; std::cout << "result3: " << result3 << std::endl; return 0; }
这里需要格外注意,在C++中,缺省参数不同并不能让编译器区分出该调用为哪一个函数重载
七.引用
引用的概念和定义
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:水浒传中,宋江叫"铁牛",李逵江湖上⼈称"黑旋风";林冲,外号豹子头;
**引用就像是变量的另一个名字,通过引用可以间接访问和操作所绑定的变量。**定义引用的语法是在变量名前加上&
符号。例如:
类型& 引用别名=引用对象;
cpp
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
//也可以给别名b取别名d,相当于还是a的别名
int& d = b;
++d;
//这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
引用的特点
必须初始化 :引用在定义时必须初始化,且一旦初始化后,就不能再绑定到其他变量。例如:
cppint num1 = 5; int num2 = 10; int& ref1 = num1; // 正确 int& ref2; // 错误,引用未初始化 ref1 = num2; // 这并不是将ref1绑定到num2,而是将num2的值赋给ref1所引用的num1
与绑定变量类型一致 :引用的类型必须与其所绑定的变量类型一致,不能绑定到不同类型的变量。
cppdouble num3 = 3.14; int& ref3 = num3; // 错误,类型不匹配
引用的应用场景
引用在实践中主要是引用传参和引用做返回值中减少拷贝提升效率和改变引用对象时同时改变被 引用对象。
作为函数参数
传递大型对象提高效率 :当函数的参数是大型对象时,使用引用传递可以避免对象的拷贝,提高函数调用的效率。例如:
cpp#include <iostream> #include <vector> // 值传递打印函数 void printValue(std::vector<int> v) { for (int i : v) std::cout << i << " "; } // 引用传递打印函数 void printRef(const std::vector<int>& v) { for (int i : v) std::cout << i << " "; } int main() { std::vector<int> bigVec = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; printValue(bigVec); // 这里会复制bigVec,费时间 printRef(bigVec); // 直接用bigVec,不复制,更快 return 0; }
修改函数外部变量:通过引用传递参数,可以在函数内部修改函数外部的变量。例如:
cppvoid swap(int& num1, int& num2) { int temp = num1; num1 = num2; num2 = temp; } int main() { int a = 5, b = 10; swap(a, b); std::cout << "a: " << a << ", b: " << b << std::endl; return 0; }
在
swap
函数中,通过引用参数直接修改了main
函数中的a
和b
的值。作为函数返回值
返回引用以提高效率和实现链式调用 :当函数返回一个对象时,如果返回的是对象的引用,可以避免对象的拷贝,提高效率。而且,返回引用还可以实现链式调用。例如:
cpp#include <iostream> #include <string> // 定义Person类,用于表示人的相关信息 class Person { public: std::string name; // 成员变量,用于存储人的姓名 int age; // 成员变量,用于存储人的年龄 // 成员函数,用于设置人的姓名,并返回当前Person对象的引用 // 通过返回引用,可以实现链式调用,方便连续设置多个成员变量的值 Person& setName(const std::string& newName) { name = newName; // 将传入的新姓名赋值给当前对象的name成员变量 return *this; // 返回当前对象的引用,*this表示当前对象本身 } // 成员函数,用于设置人的年龄,并返回当前Person对象的引用,同样为了支持链式调用 Person& setAge(int newAge) { age = newAge; // 将传入的新年龄赋值给当前对象的age成员变量 return *this; // 返回当前对象的引用 } }; int main() { Person person; // 创建一个Person类的对象person // 链式调用setName和setAge函数来设置person对象的姓名和年龄 // 首先调用person.setName("Alice"),会设置person对象的姓名为"Alice", // 由于setName函数返回了person对象的引用,所以可以紧接着调用setAge函数, // 对同一个person对象设置年龄为25 person.setName("Alice").setAge(25); // 输出person对象的姓名和年龄信息 std::cout << "Name: " << person.name << ", Age: " << person.age << std::endl; return 0; // 主函数结束,返回0表示程序正常退出 }
在上述代码中,
Person
类的setName
和setAge
函数返回引用,使得可以连续调用这两个函数,实现了链式调用的效果。
注意返回引用的有效性 :返回引用时要确保所引用的对象在函数返回后仍然有效。如果返回的是局部变量的引用,将会导致错误,因为局部变量在函数结束时就被销毁了。例如:
cpp
int& wrongFunction() {
int num = 10;
return num; // 错误,返回局部变量的引用
}
八.const引用
基本语法
在 C++ 中,const
引用的声明形式是在类型和引用符号之间加上const关键字。例如:
cpp
int num = 10;
const int& const_ref = num;
const 引用的特性
保护所引用对象的不可修改性
一旦一个变量被const引用绑定,就不能通过该引用修改所绑定变量的值。例如:
cppint a = 5; const int& ref_a = a; // ref_a = 10; // 这是错误的,不能通过const引用修改值
延长临时对象的生命周期
当const引用绑定到一个临时对象时,这个临时对象的生命周期会延长至const引用的生命周期结束。例如:
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
cppconst int& ref = 10 + 20; // 这里10 + 20是一个临时对象 // 临时对象因为被const引用绑定,其生命周期延长,可以在此处使用ref
类型转换的兼容性
**const引用可以绑定到不同类型的对象,但会创建一个临时对象来进行类型转换。**例如:
cppdouble d = 3.14; const int& ref_d = d; // 这里会创建一个临时的int对象,其值为3
需要注意的是:**const引用可以引用一个const对象,但是必须用const引用。const引用也可以引用普通对象,因为对象的访问权限在引用过程中可以缩小,但是不能放大。**例如
cpp
#include <iostream>
class A {
public:
int data;
};
int main() {
// const对象
const A const_a = {10};
// 必须使用const引用来引用const对象
const A& ref_const_a = const_a;
// ref_const_a.data = 20; // 错误,不能通过const引用修改const对象的值
// 普通对象
A a = {20};
// const引用可以引用普通对象
const A& ref_a = a;
// ref_a.data = 30; // 错误,不能通过const引用修改对象的值
return 0;
}
兄弟们共勉 !!!
码字不易,求个三连
抱拳了兄弟们!