文章目录
🎯引言
C++作为一门广泛应用的编程语言,以其强大的功能和灵活性,成为众多开发者的首选。在这个全新的C++专栏中,我们将从基础入手,带领大家逐步深入了解这门语言的精髓。作为专栏的第一篇文章,我们将探索一些C++中的核心基础知识,包括命名空间、inline
函数、nullptr
、重载与引用。这些概念不仅是理解C++的起点,也将为后续的学习和实际编程打下坚实的基础。
👓C++入门基础
1.c++发展历史
C++的历史可以追溯到20世纪70年代末,当时贝尔实验室的计算机科学家Bjarne Stroustrup开始开发一种名为“C with Classes”的语言。Stroustrup希望结合C语言的高效性与Simula语言的面向对象特性,从而创建一种既能支持系统级编程,又具备高级抽象能力的编程语言。
关键发展阶段:
- 1979年:C with Classes的诞生
Bjarne Stroustrup开始在贝尔实验室开发一种基于C语言的扩展版本,加入了类(Classes)、继承(Inheritance)等面向对象的特性。 - 1983年:C++正式命名
Stroustrup的语言正式命名为C++,其中“++”象征着C语言的增强和扩展。C++很快受到开发者的欢迎,成为一种功能强大且灵活的编程语言。 - 1985年:C++的首次公开发行
C++的第一个商业版本发布,同时Stroustrup的经典著作《The C++ Programming Language》也出版,为C++的推广奠定了基础。 - 1990年代:标准化进程
随着C++的普及,开发者对语言的标准化需求愈发强烈。1998年,国际标准化组织(ISO)发布了第一个C++标准,称为C++98,这一版本成为C++语言的重要里程碑。 - 2000年代:C++的持续演进
随着计算机硬件和软件的快速发展,C++也不断演进。2003年发布了C++03标准,这是C++98的一个小型修订版本。随后,C++的发展进入了相对平静的阶段。 - 2011年:C++11的发布
C++11是C++语言的重大更新,引入了包括auto
、nullptr
、lambda表达式、智能指针、并发支持等众多新特性,极大地增强了语言的表达能力和现代化特性。 - 后续标准(C++14、C++17、C++20)
在C++11的基础上,C++标准持续更新。C++14进一步改进了C++11的一些特性;C++17引入了文件系统库、并行算法等新功能;C++20则加入了模块、协程、概念等重大特性,标志着C++的又一次重要进化。 - C++23及未来
C++23在2023年发布,进一步改进了现有特性,并添加了一些新功能。随着硬件和编程需求的不断变化,C++也在不断进化,以适应新的挑战。
2.C++在工作领域的应用
- 系统软件开发
C++在操作系统、驱动程序、和低级别系统工具开发中占据着重要地位。由于其高效性和对硬件的直接控制,C++被用于开发像Windows、Linux内核部分等系统级软件。此外,许多设备驱动程序和嵌入式系统也依赖C++编写,确保了硬件的高效运行和可靠性。
- 游戏开发
游戏开发是C++的一个重要应用领域。由于C++的高性能和对硬件的良好控制能力,许多3D游戏引擎(如Unreal Engine、Unity等)都使用C++开发。C++的高效性使得游戏能够在资源密集型的环境下运行,并为复杂的物理引擎、图形渲染和实时响应提供支持。
- 金融工程
在金融领域,C++被广泛用于高频交易系统、定量分析和风险管理工具的开发。由于金融应用对性能的极高要求,以及需要处理大量实时数据,C++的高效性和强大的数值计算能力使其成为金融工程师的首选语言。
- 嵌入式系统
嵌入式系统中常常需要在资源有限的环境下运行高效、可靠的代码,因此C++因其对硬件的直接控制能力以及高效性,被广泛应用于各种嵌入式设备中,如家电、汽车控制系统、医疗设备和物联网(IoT)设备。
- 图形和多媒体处理
C++在图形处理、图像处理、和多媒体应用中具有重要作用。许多图形处理库(如OpenCV、DirectX、OpenGL等)都是使用C++编写的,这使得C++成为开发高性能图形处理和视频处理应用的理想选择。
- 大型软件开发
C++的面向对象特性和模块化设计能力,使其在开发大型复杂软件(如办公软件、浏览器、数据库系统等)时非常有效。C++的强类型系统和内存管理控制能力,可以帮助开发者编写高性能、可维护的大型软件系统。
- 科学计算与模拟
在科学研究和工程领域,C++常用于开发模拟和计算软件。由于科学计算需要处理大量的数值运算,C++的高效性和丰富的数学库(如Eigen、Boost等)使其成为实现复杂算法和精确计算的理想语言。
- 网络通信
C++在开发网络通信协议、服务器、客户端应用程序中得到了广泛应用。C++的高性能特性使其特别适合需要处理大量并发连接和实时数据传输的网络应用程序,如网络服务器、P2P网络、即时通讯软件等。
- 自动驾驶与人工智能
在自动驾驶和人工智能领域,C++也得到了广泛应用。由于这些应用对实时性和性能的严格要求,C++成为开发自动驾驶算法、传感器融合、实时决策系统的重要语言。此外,许多深度学习框架(如TensorFlow的底层部分)也使用了C++来实现高效的模型训练和推理。
3.命名空间
3.1命名空间的意义
命名空间(namespace)是C++中的一种重要特性,用于组织代码并避免命名冲突。随着项目规模的扩大和代码库的复杂化,命名空间的价值愈发显著。
- 避免命名冲突
在大型项目中,不同的开发人员或团队可能会编写包含相同名称的类、函数、变量等。命名空间允许将这些名称分组到不同的逻辑单元中,避免命名冲突。
- 组织代码
命名空间为代码提供了更好的组织方式,尤其是当项目涉及多个模块或子系统时。通过将相关类、函数、变量等放置在同一个命名空间中,开发者可以更清晰地表达它们之间的关系,并简化代码的管理。
- 防止污染全局命名空间
将所有代码放在全局命名空间中会增加命名冲突的风险,并导致代码难以维护。命名空间可以有效地限制代码的作用域,防止不必要的名称暴露在全局范围内。例如,库开发者可以将库中的所有功能封装在一个特定的命名空间中,避免对使用者的代码产生影响。
3.2命名空间的定义和使用
命名空间的定义
在C++中,namespace
(命名空间)用于将一组相关的标识符(如类、函数、变量等)组织在一起,从而避免命名冲突并提高代码的可读性和可维护性。命名空间的定义通常使用关键字namespace
,后跟命名空间的名称,然后在大括号内定义该命名空间中的内容。如:
namespace 名称
{
//命名空间的内容
}
命名空间的使用示例:
#include <iostream>
using namespace std;
double add(double a, double b) {
cout<<"add(double a, double b)"<<endl;
}
double subtract(double a, double b) {
cout<<"subtract(double a, double b)"<<endl;
}
namespace MathFunctions {
void add(double a, double b) {
cout<<"MathFunctions::add(double a, double b)"<<endl;
}
void subtract(double a, double b) {
cout<<"MathFunctions::subtract(double a, double b)"<<endl;
}
}
int main() {
add(0.0,0.0);
subtract(0.0,0.0);
//使用域作用符::去访问命名空间的相关内容9
MathFunctions::add(0.0,0.0);
MathFunctions::subtract(0.0,0.0);
return 0;
}
运行结果
add(double a, double b)
subtract(double a, double b)
MathFunctions::add(double a, double b)
MathFunctions::subtract(double a, double b)
关键点:
- 命名空间名称:
MathFunctions
是命名空间的名称,可以任意指定,但应当具有描述性,以便明确其内容的用途。 - 访问命名空间中的成员:在使用命名空间中的变量或函数时,需要使用命名空间名称作为前缀,并通过
::
操作符来访问。例如,MathFunctions::add
调用了MathFunctions
命名空间中的add
函数。
using关键字展开
- 展开命名空间部分使用权限
代码示例:
#include <iostream>
using namespace std;
namespace Han
{
int x=10;
int add(int x,int y)
{
return x+y;
}
}
//使用using关键字 展开Han这个命名空间的全部权限
using namespace Han;
int main()
{
//展开之后就不用使用域操作符::来访问命名空间的内容了
cout<<x<<endl;
cout<<add(10,20)<<endl;
return 0;
}
- 展开命名空间全部的使用权限
代码示例:
#include <iostream>
using namespace std;
namespace Han
{
int x=10;
int add(int x,int y)
{
return x+y;
}
}
//使用using关键字 咱开命名空间部分内容
using Han::x;
int main()
{
//展开之后就不用使用域操作符::来访问命名空间的内容了
cout<<x<<endl;
//add没有被展开 下面的代码编译时会报错
//cout<<add(10,20)<<endl; err
return 0;
}
注:
通过上面的学习,我们就能明白,为什么在cpp代码中,刚开始都会写上using namespace std;
这段代码了把,因为cpp的设计者,在设计的过程中为了避免与原先C语言中的某些内容重复,所以讲CPP的内容单独放入了一个命名空间,防止冲突.
#include <iostream>
//如果我们不使用using namespace std;
int main()
{
//在使用cout时要通过域操作符在能实现打印操作
std::cout<<"hello"<<std::endl;
return 0;
}
//一般在大型项目中,都不会将std这个命名空间完全展开,避免冲突
//但我们日常练习时,可以直接展开,影响不大
命名空间的嵌套
命名空间可以嵌套定义,以组织更复杂的代码结构:
namespace Outer {
namespace Inner {
void function() {
// 实现
}
}
}
int main() {
Outer::Inner::function(); // 访问嵌套命名空间中的函数
return 0;
}
4.C++的输入与输出
本次只讲解最简单的用法,设计到类的相关概念先不讲,知道怎么用即可.C++中C语言的printf
函数依然可以使用
在C++中,cout
和 cin
是用于标准输入和输出的主要对象。它们分别属于 std
命名空间中的 ostream
和 istream
类,用于处理控制台的输入输出操作。以下是 cout
和 cin
的最基础讲解及用法。
cout
:标准输出流
cout
是一个对象,用于向标准输出设备(通常是控制台)打印数据。它是 std::ostream
类的实例。
输出数据
#include <iostream> // 包含输入输出流头文件
int main() {
int age = 25;
std::cout << "Hello, world!" << std::endl;
std::cout << "Age: " << age << std::endl;
return 0;
}
在这个例子中,std::cout
用于打印文本和变量的值。<<
操作符用于将数据插入到输出流中。std::endl
用于输出换行符并刷新输出缓冲区。
cin
:标准输入流
cin
是一个对象,用于从标准输入设备(通常是键盘)读取数据。它是 std::istream
类的实例。
读取数据
#include <iostream>
int main() {
int age;
std::cout << "Enter your age: ";
std::cin >> age; // 从标准输入读取数据并存储到变量 age 中
std::cout << "Your age is: " << age << std::endl;
return 0;
}
在这个例子中,std::cin
用于从键盘读取用户输入的数据,并将其存储在变量 age
中。
5.缺省参数
在C++中,缺省参数(默认参数)是函数参数的一个重要特性,允许在调用函数时省略某些参数的值。如果调用函数时没有提供这些参数的值,函数会使用定义时指定的缺省值。这可以使函数更加灵活,并且简化了函数的调用。
5.1定义缺省参数
在函数声明中可以为一个或多个参数指定缺省值。缺省值在函数声明中提供,并且通常在函数定义中不需要再次提供。缺省参数从右到左提供,这意味着在给定缺省值的参数后,所有右侧的参数也必须有缺省值。
基本语法:
returnType functionName(type param1 = defaultValue1, type param2 = defaultValue2, ...);
示例:
#include <iostream>
using namespace std;
int add(int x =10, int y =20)
{
return x + y;
}
int main()
{
cout<<add()<<endl;
return 0;
}
输出:
30
5.2缺省参数的位置
缺省参数必须从右向左提供。例如,以下声明是非法的:
// 错误示例:param1 没有缺省值,param2 和 param3 有缺省值
void function(int param1 = 0, int param2, int param3 = 10);
这种情况下,缺省值的参数必须在没有缺省值的参数之后提供。
5.3缺省参数不能在函数声明和定义中同时出现
函数声明和函数定义在同一文件下
#include <iostream>
using namespace std;
int add(int x = 10, int y = 20);
int add(int x =10, int y =20)
{
return x + y;
}
int main()
{
cout << add() << endl;
return 0;
}
vs报错:
正确版本是,声明或者定义中任意一个给缺省参数
函数声明和函数定义不在同一文件下
//add.h文件中
#include <iostream>
using namespace std;
int add(int x = 10, int y = 20);
//add.c文件中
int add(int x , int y )
{
return x + y;
}
上诉情况函数声明必须加缺省值,函数定义不能加
6.函数重载
函数重载(Function Overloading)是C++中的一种特性,允许在同一个作用域中定义多个具有相同名称但参数列表不同的函数。通过函数重载,程序员可以创建多个功能类似但接受不同参数的函数,从而提高代码的可读性和重用性。
6.1函数重载的基本规则
- 函数名称相同:所有重载的函数必须具有相同的名称。
- 参数列表不同:每个重载的函数必须具有不同的参数列表。参数列表的不同可以通过参数的数量、类型或顺序来区分。
- 返回类型不影响重载:函数的返回类型不会影响重载。如果两个函数只有返回类型不同,但参数列表相同,它们不能被重载。
示例:
#include <iostream>
// 重载函数定义
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
int main() {
print(42); // 调用 void print(int)
print(3.14); // 调用 void print(double)
return 0;
}
函数重载(Function Overloading)是C++中的一种特性,允许在同一个作用域中定义多个具有相同名称但参数列表不同的函数。通过函数重载,程序员可以创建多个功能类似但接受不同参数的函数,从而提高代码的可读性和重用性。
函数重载的基本规则
- 函数名称相同:所有重载的函数必须具有相同的名称。
- 参数列表不同:每个重载的函数必须具有不同的参数列表。参数列表的不同可以通过参数的数量、类型或顺序来区分。
- 返回类型不影响重载:函数的返回类型不会影响重载。如果两个函数只有返回类型不同,但参数列表相同,它们不能被重载。
示例
示例 1: 基本函数重载
#include <iostream>
// 重载函数定义
void print(int i) {
std::cout << "Integer: " << i << std::endl;
}
void print(double d) {
std::cout << "Double: " << d << std::endl;
}
int main() {
print(42); // 调用 void print(int)
print(3.14); // 调用 void print(double)
return 0;
}
在这个示例中,print
函数被重载了二次,分别处理 int
、double
类型的参数。编译器根据传递的参数类型选择相应的重载版本。
示例 2: 参数数量不同
#include <iostream>
// 重载函数定义
void display() {
std::cout << "No arguments" << std::endl;
}
void display(int i) {
std::cout << "Integer: " << i << std::endl;
}
void display(int i, double d) {
std::cout << "Integer: " << i << ", Double: " << d << std::endl;
}
int main() {
display(); // 调用 void display()
display(5); // 调用 void display(int)
display(5, 3.14); // 调用 void display(int, double)
return 0;
}
在这个示例中,display
函数被重载了三次,分别处理不同数量的参数。
示例3:缺省参数和重载
#include <iostream>
// 重载函数定义
void display() {
std::cout << "No arguments" << std::endl;
}
void display(int i=10) {
std::cout << "Integer: " << i << std::endl;
}
int main() {
display();
//调用不明确
return 0;
}
虽然上述用法符合重载,但是语义不明确,编译器无法识别
7.引用
在C++中,引用(Reference)是一个强大的特性,允许函数或变量通过别名(别名)来访问已有的变量。引用是一个类型安全的指针,提供了更简洁、更安全的语法,用于直接操作原始变量而无需使用指针语法。引用在C++中有多种用途,包括函数参数传递、返回值、以及在某些情况下进行数据绑定。
7.1 基本语法
引用的基本语法使用 &
符号来定义一个引用类型的变量。引用必须在定义时初始化,并且一旦绑定到一个变量后,不能再更改为绑定到其他变量。
#include <iostream>
int main() {
int a = 10;
int& ref = a; // ref 是 a 的引用
std::cout << "a: " << a << std::endl; // 输出 10
std::cout << "ref: " << ref << std::endl; // 输出 10
ref = 20; // 修改引用所绑定的变量的值
std::cout << "a: " << a << std::endl; // 输出 20
std::cout << "ref: " << ref << std::endl; // 输出 20
return 0;
}
7.2 引用作为函数参数
引用可以用于函数参数,以避免复制大对象并允许函数修改传递给它的变量。
示例:
#include <iostream>
void increment(int& value) {
++value;
}
int main() {
int number = 5;
increment(number);
std::cout << "Number after increment: " << number << std::endl; // 输出 6
return 0;
}
在这个例子中,increment
函数接受一个 int
类型的引用作为参数,并增加它的值。由于传递的是引用,函数修改的是原始变量 number
。
7.3 引用作为函数返回值
函数可以返回引用,这样可以允许调用者修改函数返回的对象,或者提高效率,避免不必要的复制。
示例:
#include <iostream>
int& getElement(int* array, int index) {
return array[index]; // 返回数组元素的引用
}
int main() {
int array[5] = {1, 2, 3, 4, 5};
getElement(array, 2) = 10; // 修改数组的第三个元素
std::cout << "Modified array[2]: " << array[2] << std::endl; // 输出 10
return 0;
}
在这个例子中,getElement
函数返回一个数组元素的引用,因此可以直接修改数组元素的值。
7.4引用的注意事项
- 引用必须初始化
引用在声明时必须立即初始化,并且一旦初始化后不能再更改所绑定的对象。
int x = 10;
int& ref = x; // 正确:引用必须初始化
错误示例:
int x = 10;
int& ref; // 错误:引用必须初始化
ref = x; // 错误:引用在声明时未初始化
- 避免返回局部变量的引用
返回局部变量的引用会导致引用悬挂,指向一个已经被销毁的对象。
int& getLocalVariable() {
int local = 5;
return local; // 错误:local 在函数结束时被销毁
}
改进:
int global = 5;
int& getGlobalVariable() {
return global; // 正确:global 的生命周期与程序一致
}
7.5const引用
权限只能缩小不能放大,将贯穿下面所讲的内容
1.const
引用的基本概念
-
定义和语法
const
引用的基本语法是将const
关键字放在引用类型前:const int& ref = someInt;
在这个例子中,
ref
是一个const
引用,引用的someInt
变量不能通过ref
修改。 -
只读访问
通过
const
引用访问的对象只能读取其值,而不能修改。例如:void printValue(const int& value) { std::cout << "Value: " << value << std::endl; // value = 10; // 错误:不能修改 const 引用的值 }
在
printValue
函数中,value
是一个const
引用,函数体内不能修改value
的值。
2.引用权限的缩小与放大
- 权限缩小:当你使用
const
引用时,实际上是在缩小对引用对象的操作权限。const
引用只能读取对象的值,不能修改它。这种方式在功能上限制了引用所能执行的操作,从而确保对象的安全性和稳定性。 - 权限放大:与之相对,如果尝试将
const
对象的引用绑定到非常量引用,这是不允许的。这是因为它会扩展或放大对象的操作权限,这可能导致意外的修改或违反对象的只读特性。
非常量引用允许对其引用的对象进行修改。这意味着通过非常量引用可以改变被引用对象的值。非常量引用的声明不包含 const
关键字。
常量引用用于对对象进行只读访问,不允许通过常量引用修改对象的值。常量引用的声明包含 const
关键字。即使引用的是非 const
对象,常量引用也只能读取对象的值,不能修改它。
代码示例
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a; // b 是 a 的引用,b 可以修改 a
const int& d = a; // d 是 a 的 const 引用,d 只能读取 a,不能修改
const int d2 = 20; // d2 是一个常量,值为 20
// int& e = d2; // 错误:不能将 const int 引用 d2 绑定到非常量引用 e
const int& f = d2; // 正确:const int& 可以绑定到 const int
// 进一步解释:
// 1. int& b = a; // 这是合法的,因为 b 是非常量引用,它可以修改 a。
// 2. const int& d = a; // 这是合法的,d 是 const 引用,只能读取 a,不能修改。
// 3. const int d2 = 20;// 这是合法的,d2 是常量。
// 4. int& e = d2; // 错误:d2 是 const int,不能被绑定到非常量引用 e。
// 5. const int& f = d2;// 这是合法的,f 是 const 引用,能够绑定到 const int。
return 0;
}
- 总结
const
引用:允许只读访问,但不允许修改对象的值。引用权限只能缩小,不能放大。- 引用的类型一致性:
const
引用可以绑定到常量对象,也可以绑定到非const
对象,但不能将常量对象绑定到非常量引用。 - 权限放大与缩小:通过
const
引用访问对象时,权限被缩小为只读,确保不会修改对象;尝试将const
对象绑定到非常量引用会失败,因为这会扩大访问权限,可能导致不期望的修改。
8.inline和nullptr
8.1. inline
定义
inline
是一个关键字,用于请求编译器将函数调用的代码“内联”到调用点,而不是通过标准的函数调用机制进行。这可以减少函数调用的开销,尤其是在小而频繁调用的函数中。inline
提示编译器将函数体直接插入到每个调用点,从而避免了函数调用的开销(如参数传递和返回地址的处理)。
语法
inline int add(int a, int b) {
return a + b;
}
使用场景
- 小而频繁调用的函数:适用于函数体非常简单的场景,如 getter/setter 函数,或者简单的数学计算函数。
- 避免函数调用开销:通过内联函数来减少函数调用的开销,尤其是当函数被频繁调用时。
注意事项
- 编译器的决定:
inline
是一个建议,编译器可能会选择忽略它。编译器会根据函数的复杂性和优化策略来决定是否进行内联。 - 代码膨胀:过度使用
inline
可能导致代码膨胀(即编译出的代码变得非常庞大),因为每个调用点都会插入函数体。
示例
#include <iostream>
inline int square(int x) {
return x * x;
}
int main() {
std::cout << square(5) << std::endl; // 编译器可能会将 square 函数体内联到这里
return 0;
}
8.2 nullptr
定义
nullptr
是一个C++11引入的关键字,用于表示空指针。它是一个类型安全的空指针常量,能够明确表示指针不指向任何对象。nullptr
替代了传统的 NULL
宏,提供了更强的类型安全性。
语法
int* p = nullptr; // p 是一个空指针
使用场景
- 初始化指针:用
nullptr
初始化指针,以确保指针不指向任何有效地址。 - 类型安全:
nullptr
可以避免与整数混淆,从而提供类型安全,尤其是在函数重载中。
注意事项
- 类型安全:
nullptr
是一个类型安全的空指针常量,不会隐式转换为整型,这避免了NULL
在某些情况下可能导致的歧义。 - 重载解析:在函数重载中,
nullptr
可以帮助编译器更好地确定调用哪个函数,因为它不会被隐式转换为整型。
🥇结语
通过对命名空间、inline
函数、nullptr
、重载和引用的学习,我们为C++的探索开启了第一步。这篇文章作为专栏的开篇,旨在为大家奠定一个坚实的基础,帮助你在未来的C++学习中更加从容应对复杂的编程挑战。希望你能在接下来的系列文章中,继续保持学习热情,一步步深入C++的世界,掌握更多编程技能。