目录
- C++发展史,C++语言的特性
- C++新增关键字
- namespace关键字
- C语言的命名缺陷(重定义现象)
- 域与指定访问操作符 “::”
- 命名空间域详解
- namespace std
- C++的输入与输出
- 函数重载
- 什么是重载,重载的几种常见形态
- 重载的作用
- 注意不构成重载的情况
- 缺省参数
- 1.全缺省
- 2.半缺省
- 注意事项
- auto关键字
- auto关键字的注意事项
- 引用
- 什么是引用
- 引用应用场景和作用
- 函数传值调用的本质(重要)
- 返回引用虽好,切忌不可滥用
- 引用和“权限const”的关系
- 引用和指针的区别(重要)
- 引用 VS 指针(初始化与绑定)
- 引用 VS 指针(多级间接访问)
- 引用 VS 指针(传参)
- 引用 VS 指针(安全性)
- C++语法糖:基于范围的for循环
- C++的空指针nullptr
- C语言null指针的局限
C++发展史,C++语言的特性
C++由丹麦计算机科学家Bjarne Stroustrup在贝尔实验室工作时开发。
Stroustrup最初的目标是为系统级编程和嵌入式系统创建一种改进的C语言版本,以支持更高效的代码组织方式。他最初称之为“带类的C”(“C with Classes”),它引入了面向对象编程的概念,如类、继承、封装。
1985年:发布了第一个商业版本,迅速获得了认可,并开始被广泛应用于各种领域
1990年:发布了带有标准模板库(STL)的版本,进一步增强了其功能性和灵活性,STL是C++的核心之一,不会STL库是不敢说自己会C++的
后续会详细介绍STL库
1998年:ISO/IEC 14882:1998标准发布,标志着C++成为了一种国际标准语言。这被称为C++98(C++98是C++非常重要的版本)
2011年:C++11发布,带来了许多重要的新特性,如自动类型推导(auto)、lambda表达式、智能指针等,极大提升了语言的现代性和易用性(C++11对C++语言的发展来说极其重要,它标志着C++的一次重大进化,后续会详细介绍C++11引入的新特性)
语言特性 1 :对硬件的精密操控 :
C++允许对硬件进行精细控制,包括内存管理和直接访问硬件资源,使得它非常适合需要高效率的应用场景,如操作系统、游戏引擎、嵌入式系统
语言特性 2:多范式编程
C++支持多种编程范式,包括过程化编程、面向对象编程(OOP)、泛型编程和函数式编程。这种灵活性使开发者可以根据具体需求选择最合适的编程方式。
语音特性3 :兼容C语言
C++被设计为与C语言高度兼容,允许C++代码调用C库,并且可以直接包含C头文件。这使得从C迁移到C++变得相对容易。
C++还支持并发,零抽象开销等特性…,现在就让我们正式进入C++吧
C++新增关键字
class 定义类支持面向对象编程
new 动态分配内存并调用构造函数
delete 释放动态分配的内存并调用析构函数
public 定义类的公有成员
private 定义类的私有成员
protected 定义类的受保护成员
this 指向当前对象的指针
virtual 声明虚函数,支持多态
override 显式声明重写基类的虚函数(C++11 引入)
final 禁止派生类重写虚函数或禁止类被继承(C++11 引入)
friend 声明友元函数或类,允许访问私有成员
template 定义模板,支持泛型编程
typename 在模板中声明类型参数或指示依赖类型
namespace 定义命名空间,避免命名冲突
using 引入命名空间成员或定义类型别名
bool 布尔类型(true 或 false)
true 布尔值真
false 布尔值假
nullptr 表示空指针(C++11 引入)
constexpr 声明常量表达式(C++11 引入)
auto 自动类型推导(C++11 引入)
(这些关键字非常重要,后续文章这些关键字都会介绍)
namespace关键字
C语言的命名缺陷(重定义现象)
#include<stdio.h>int rand = 0;
int main()
{printf("%d\n", rand);return 0;
}
当我们包含stdlib头文件的时候上述代码片就会有重定义的错误
在几百万行甚至几千万行的工程中,我们一定会遇到大量的重定义行为
由此工程师们引入了关键字namespase支持“重定义”行为
域与指定访问操作符 “::”
一般的我们把域分为三类,命名空间,全局,局部
使用 : : 操作符就可以访问全局域的x
命名空间域详解
命名空间域在全局作用域中有自己的独立的区域
使用namespace定义命名空间
下图可见text和text2互不干扰,相互独立
那么我们该怎么访问命名空间域里面的成员呢
当然就是通过上述的域指定访问操作符 ::
还有一种方式是直接展开整个命名空间
注意了,滥用using展开整个命名空间会导致重定义,目标不明确的现象
滥用using导致x不明确
namespace std
std命名空间是C++标准库使用的命名空间。它包含了所有标准库的组件,这些标准库的
组件分部在各种头文件中
如容器(vector, list, map等)、算法(sort, find等)、流(iostream, fstream等)
使用std命名空间同样有两种方法:
- using namespace std;
- 使用 :: ,如 std::cout << “hello world” << std::endl; 指定访问std命名空间
在平常做小型实验或小型demo中,我们为图方便可以使用using展开std
在中大型工程中,为了避免与std的成员命名冲突,绝对不能直接using展开std
C++的输入与输出
C++的输入流cout
通常用于将数据输出到控制台,它与插入运算符 << 结合使用,可以输出各种类型的数据,如字符串、数字等。
有了 cout 输出的知识,我们可以写出第一个C++程序 “hello world”了
C++的输出流 cin
通常用于从控制台读取数据。它与提取运算符 >> 结合使用,可以从用户那里获取输入,并将其存储到变量中。
他们都被定义在 std 命名空间中
函数重载
什么是重载,重载的几种常见形态
生活中的函数重载:
微波炉通常有多种加热模式,可以根据你放进去的食物类型和数量,自动调整加热时间和功率,而不是A微波炉只能加热5min,B微薄炉只能加入10min… 这就是一种“函数重载”的体现。
函数重载: 是函数的一种特殊情况,
C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这些同名函数的 形参列表 ( 参数个数 或 类型 或 类型顺序 ) 不同 ,常用来处理实现功能类似数据类型不同的问题。
注意:对返回值没有任何要求。
如果函数名、参数个数、类型、类型顺序均相同,但返回值不同,并不构成重载,而是直接报错
//参数类型不同的函数重载
//整数相加
int add(int a, int b) {std::cout << "add two integer: ";return a + b;
}// 处理两个浮点数相加
double add(double a, double b) {std::cout << "add two double: ";return a + b;
}
//参数顺序不同的函数重载
// 处理浮点数和整数相加
double add(int a, double b) {std::cout << "add a integer and a double: ";return a + b;
}// 调换顺序
double add(double a, int b) {std::cout << "add a double and a integer: ";return a + b;
}
//参数个数的缺省
int add(int a, int b) {std::cout << "add two integer: ";return a + b;
}// 处理两个浮点数相加
int add(int a) {std::cout << "a integer";return a;
}
重载的作用
函数重载的语法支持,让我们不用对仅参数不相同要执行的逻辑相同的,避免为每种不同的输入类型或参数组合创建新的函数名称。这使得代码更加简洁,并且更容易理解和维护。
注意不构成重载的情况
//不构成函数重载的情况
double add(int a, double b) {std::cout << "add a integer and a double: ";return a + b;
}// 调换顺序
double add(int c, double b) {std::cout << "add a double and a integer: ";return a + b;
}
//上述代码片的参数类型和顺序都是一样的,只不过参数换了一个名字而已
//故不构成函数重载
缺省参数
生活上的缺省参数
用户可以选择商品的基本信息(如商品名称),也可以选择额外的属性(如颜色、尺寸等)。如果没有特别指定,系统会提供默认值。
- 购买一件T恤(默认颜色和尺码)
(这就是缺省的体现)- 购买一件红色L号的T恤(指定颜色和尺码)
1.全缺省
顾名思义,全缺省就是对所有形式参数带上默认值
1.什么参数都不传,默认使用的就是缺省值
>传入了参数就不使用缺省值了
2.半缺省
半缺省就是给部分参数设置缺省值
注意事项
1.半缺省的顺序必须从右往左:
是传参是从左到右的,若从左到右设置缺省,实际参数任然要传入N个的参数
如图,我们必须先给 a 传参,后给b传参,a的缺省值形同虚设就没有了意义
2.当缺省参数遇上函数重载:
如图,两个add函数构成参数个数类型的函数重载
但是由于第一个add参数a缺省导致第185行调用add()函数二义性
编辑器不确定到底是调第一个add还是第二个add故报错
auto关键字
auto关键字是C++11新引入的特性,具有自动推到类型的作用
auto i = 42; // i 是 int 类型
auto d = 3.14; // d 是 double 类型
auto s = "Hello"; // s 是 const char* 类型
auto str = std::string("Hello"); // str 是 std::string 类型
当变量的类型非常复杂或冗长时,使用 auto 可以显著简化代码。例如,使用迭代器时:
(未来的文章会详细介绍迭代器,现在你只要知道迭代器类似于指针,它的类型非常冗长)
std::vector<int> vec = {1, 2, 3, 4, 5};
for (std::vector<int>::iterator it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";
}
使用auto自动推到迭代器类型后
for (auto it = vec.begin(); it != vec.end(); ++it) {std::cout << *it << " ";
}
auto关键字的注意事项
初始化时不能仅声明
auto x; // 错误:缺少初始化表达式
数组类型的推导涉及到多个维度和元素类型,使得推导过程变得复杂。
不能直接用auto 定义数组
auto arr[] = {1, 2, 3}; // 错误不能直接用auto定义数组
引用
什么是引用
引用本质上是给一个对象起了一个“外号”,
在我国四大名著《水浒传》中
说到及时雨我们会想到宋江
说到黑旋风我们会想到李逵
宋江和及时雨是一个人,李逵和黑旋风是一个人
引用也是如此,被引用的对象和引用对象是一个对象(不会开额外的空间)
如图,a和b的地址相同
修改a,b也修改了
引用应用场景和作用
1. 作为形式参数传参,减少拷贝构造:
函数传值调用的本质(重要)
传值调用会先生成实参的临时拷贝对象,然后把临时拷贝对象赋值给形式参数
由此可见,若是传值非常大的结构体对象,中间拷贝形成临时对象代价非常大
传引用则不需要形成临时拷贝对象然后赋值,大大提高了效率
整个过程中 a,b指向同一块地址,改变a会改变b
- 作为返回值返回,减少拷贝构造
和传值调用一样,传值返回会先生成一个临时的对象,然后把临时拷贝对象赋值给接受者
若返回引用则不需要创建临时对象
返回引用虽好,切忌不可滥用
看了返回引用的好处之后,千万不要觉得返回引用就是比返回值更好:
切记不可返回栈空间临时对象引用
栈空间用于存储函数的局部变量。
当函数执行结束时,栈帧会被销毁,局部变量的内存会被释放。
返回栈空间的引用后,引用指向的内存已经无效。
int& getLocalReference() {int localVar = 42; // 局部变量,存储在栈空间return localVar; // 错误:返回局部变量的引用
}int main() {int& ref = getLocalReference(); // 获取局部变量的引用std::cout << ref << std::endl; // 未定义行为:访问已释放的栈空间return 0;
}
引用和“权限const”的关系
口诀,大权限(可读可改)“为所欲为”,小权限(只读不改)“唯唯诺诺”
const是小权限,引用过去后还想变成大权限?,当然不行
只能const变const
大权限,引用过去后想变成小权限?,当然可以
引用和指针的区别(重要)
引用 VS 指针(初始化与绑定)
指针:
指针是一个变量,用来存储地址
大小为4字节或8字节(取决于32位机器或64位机器)
指针可以初始化为空,后续可以指向其他对象
int a = 10;
int* ptr = &a; // ptr 指向 a 的地址
ptr = nullptr; // ptr 可以指向空//重新绑定
int a = 10, b = 20;
int* ptr = &a; // ptr 指向 a
ptr = &b; // ptr 现在指向 b
引用:
引用是一个变量的别名,用来绑定其他对象
不需要额外空间
引用必须初始化绑定一个对象,并且后续不可以换绑
// int& ref; // 错误:引用必须初始化
int a = 10;
int& ref = a; // 正确//重新绑定
int a = 10, b = 20;
int& ref = a; // ref 绑定到 a
// ref = b; // 错误:引用不能重新绑定
引用 VS 指针(多级间接访问)
只有指针的指针(二级指针),没有引用的引用
指针支持多级访问,引用不支持多级访问
int a = 10;
int* ptr = &a;
int** ptr2 = &ptr; // 指针的指针int a = 10;
int& ref = a;
// int&& ref2 = ref; // 错误:没有引用的引用
引用 VS 指针(传参)
指针作为参数,传的是地址的拷贝
因此可以通过同一指向地址改变原始内容(要改变指针本身的值要传二级指针)
而引用直接传的是对象的别名
//传的是地址的拷贝
oid func(int* ptr) {*ptr = 20;
}
int a = 10;
func(&a); // a 的值被修改为 20//传的是地址的拷贝
void func(int& ref) {ref = 20;
}
int a = 10;
func(a); // a 的值被修改为 20
引用 VS 指针(安全性)
引用必须初始化绑定一个对象,而且其未来不能换绑。故引用的安全性比指针高
int* ptr = nullptr;
*ptr = 10; // 未定义行为:空指针解引用int a = 10;
int& ref = a; // 安全
C++语法糖:基于范围的for循环
基于范围的for循环,在这个例子中element是一个局部变量,迭代时它会依次的被赋值为
容器的一个元素
这下我们学的auto关键字就派上用场了
上述例子中,element都是被容器元素赋值的对象,并不是元素本身,想要在迭代的过程中改变容器元素的值,那就要传引用(也就是传别名)
例,不传引用无法改变容器的值
C++的空指针nullptr
C语言null指针的局限
null常被定义为 整数 0 或零的空指针类型 (void*)0
null被解释成0,又能被解释成 零的空指针是非常不合理的
NULL被定义为 0 ,调用了func(int)
二义性:
NULL又可以初始化空指针,又可以被解析为0
为了解决上述NULL所带来的困扰,科学家们在C++11引入了新关键字nullptr
nullptr只能解释为指针,空指针可以隐式类型转化为任意指针类型
今天的你又一点进步了哟,明天继续加油