C++ 继承了 C 语言的高效性和灵活性,同时新增了面向对象编程的特点。这使得 C++ 既可以进行底层系统编程,又能进行面向对象的软件设计。在面向对象编程方面,C++ 支持封装、继承和多态三大特性。
💯C++ 初印象
语言的发展就像是练功打怪升级一样,也是逐步递进,由浅入深的过程。我们先来看下C++的历史版本。
😏C++的发展史:
C++还在不断的向后发展。但是:⭐现在公司主流使用还是C++98和C++11,所有大家不用追求最新,重点将C++98和C++11掌握好,等工作后,随着对C++理解不断加深,有时间可以去琢磨下更新的特性。
💯关键概念深入探索
(一)命名空间
命名空间是 C++ 中用于避免命名冲突和组织代码的重要机制。它可以将变量、函数、类等标识符包含在一个逻辑空间中,避免与其他代码中的标识符发生冲突。
🌷如图:
😏我们加入命名空间就能解决这个问题:
定义和使用方法:
可以使用namespace关键字来定义命名空间。例如:
namespace MyNamespace {
int myVariable = 10;
void MyFunction()
{
std::cout << "Hello from MyNamespace!" << std::endl;
}
}
要使用命名空间中的成员,可以使用作用域解析运算符 :: 来指定命名空间。例如:MyNamespace::MyFunction();。也可以使用using namespace关键字将命名空间中的标识符导入到当前代码中,以便更方便地使用。例如:using namespace MyNamespace;,之后就可以直接使用命名空间中的标识符,如MyFunction();。
引入命名空间成员的方式:
- 使用作用域解析运算符::对命名空间成员进行限定,如MyNamespace::myVariable。
- 使用using命名空间成员名,如using MyNamespace::myFunction;,之后可以直接使用myFunction而无需再输入命名空间前缀。
- 使用using namespace命名空间名,如using namespace MyNamespace;,声明了在本作用域中要用到命名空间MyNamespace中的成员,在使用该命名空间的任何成员时都不必再使用命名空间限定。
(二)输入输出
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
C++ 与 C 语言的输入输出方式有很大不同。在 C 语言中,通常使用scanf和printf进行输入输出操作,需要给出格式控制字符串,并且记忆繁多的占位符。而 C++ 使用iostream头文件中的cin和cout进行输入输出,更加方便易用。
cin是标准输入流,通常与流提取运算符>>结合使用。C++ 编译器会根据要输入值的数据类型,选择合适的流提取运算符来提取值,并把它存储在给定的变量中。例如:int a, b; cin >> a >> b;。
cout是标准输出流,通常与流插入运算符<<结合使用。C++ 编译器会根据要输出变量的数据类型,选择合适的流插入运算符来显示值。例如:cout << "Hello, World!" << endl;。
endl表示换行,与 C 语言中的\n作用相同,它是 “end of line” 的缩写。
(三)缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。🌟在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参
分类:
- 全缺省参数:给所有参数都指定一个默认值。例如:void TestFunc(int a = 10, int b = 20, int c = 30)。
- 半缺省参数:给部分参数指定一个默认值。例如:void TestFunc(int a, int b = 20, int c = 30)。
❗注意事项:
- 半缺省参数必须从右往左依次来给出,不能间隔着给。
- 缺省参数不能在函数声明和定义中同时出现。
- 缺省值必须是常量或者全局变量。
- C 语言不支持缺省参数。
(四)函数重载
函数重载是 C++ 的一种特殊情况,允许在同一作用域中声明几个功能类似的同名函数,只要它们的参数列表不同就可以。参数列表不同包括参数的个数不同、类型不同或顺序不同。
例如:
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;
}
这里有两个同名函数Add,但参数类型不同,一个是int类型,一个是double类型。
👀C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同👇
#include<iostream>
using namespace std;
// 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 f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
// 3、参数类型顺序不同
void 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;
}
int main()
{
Add(10, 20);
Add(10.1, 20.2);
f();
f(10);
f(10, 'a');
f('a', 10);
return 0;
}
返回值类型不同不能作为重载函数的原因是,在调用函数时,编译器是通过参数列表来确定调用哪个函数,而不是返回值类型。如果仅靠返回值类型不同来重载函数,编译器无法确定应该调用哪个函数。
(五)引用
1.引用的概念
引用就如同给已存在的变量取了一个别名。👻比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。,这里的李逵就相当于一个已存在的变量,“铁牛” 和 “黑旋风” 就是对李逵的引用。在编程中,引用变量和它所引用的原始变量共用同一块内存空间,这意味着对引用变量的操作实际上就是对原始变量的操作。
2.引用的特点
引用在定义时必须初始化:
- 这就好比你不能只说有一个别名,但不指出这个别名对应哪个具体的人。在编程中,如果不初始化引用,就不知道这个引用指向哪个具体的变量,会导致编译错误。
- 例如:
int a = 10; int& b; // 错误,引用 b 未初始化
。正确的做法是:int a = 10; int& b = a; // 正确,引用 b 初始化为变量 a
。一个变量可以有多个引用:
- 继续以李逵的例子来说,李逵除了 “铁牛” 和 “黑旋风” 这两个别名外,可能还有其他的称呼。在编程中,一个变量可以被多个引用指向,每个引用都代表这个变量的不同别名。
- 例如:
int a = 10; int& b = a; int& c = a; // 变量 a 有两个引用 b 和 c
。引用一旦引用一个实体,再不能引用其他实体:
- 就像李逵被称为 “铁牛” 后,这个 “铁牛” 的别名就不能再用来称呼其他人了。在编程中,一旦引用被初始化为某个变量,它就不能再指向其他变量。
- 例如:
int a = 10; int b = 20; int& c = a; c = b; // 这里是将变量 b 的值赋给引用 c 所指向的变量 a,而不是让引用 c 指向变量 b
。
3.常引用的应用
void TestConstRef()
{
const int a = 10;
/*int& ra = a; 该语句编译时会出错,普通引用(int&)不能绑定到常量上,
因为普通引用是可修改的,它可以通过引用改变所绑定的变量的值。
而常量不能被修改,所以会出现编译错误。*/
const int& ra = a;
/* int& b = 10; 该语句编译时会出错,10是一个常量值,不是一个变量。
普通引用必须绑定到一个变量上,不能绑定到常量值,所以会编译错误。*/
const int& b = 10;
double d = 12.34;
/*int& rd = d;该语句编译时会出错,d是一个double类型的变量,
而普通引用(int&)要求所绑定的对象类型必须完全一致。这里类型不匹配,所以会出现编译错误。*/
const int& rd = d;
}
常引用可以引用普通变量、常量或字面常量。例如:const int& ra = a;。
4.引用做参数和返回值的场景
- 做参数:可以避免值传递时的拷贝开销,提高效率。例如:void swap(int& a, int& b)。
void Swap(int& left, int& right) { int temp = left; left = right; right = temp; }
- 做返回值:可以返回一个变量的引用,实现链式操作。
int& Count() { static int n = 0; n++; // ... return n; }
5.引用与指针的区别
- 不存在空引用,引用必须连接到一块合法的内存;而指针可以为空。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象;指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化;指针可以在任何时间被初始化。
- 引用在 sizeof 中结果为引用类型的大小;指针在 sizeof 中是地址空间所占字节个数(32 位平台下占 4 个字节)。
- 有多级指针,但没有多级引用。
- 引用比指针更安全,访问实体的方式不同,指针需要显式解引用,引用编译器自己处理。
(六)内联函数
内联函数是一种在编译时将函数体插入到调用处的函数,以减少函数调用的开销。
🔥以下是用 C++ 代码解释内联函数:
#include <iostream>
// 普通函数
int addNormal(int a, int b) {
return a + b;
}
// 内联函数
inline int addInline(int a, int b) {
return a + b;
}
int main() {
int x = 5, y = 10;
// 调用普通函数
int resultNormal = addNormal(x, y);
std::cout << "Result using normal function: " << resultNormal << std::endl;
// 调用内联函数
int resultInline = addInline(x, y);
std::cout << "Result using inline function: " << resultInline << std::endl;
return 0;
}
在上述代码中,定义了两个函数addNormal
和addInline
,分别作为普通函数和内联函数实现两个整数相加的功能。在main
函数中分别调用这两个函数,可以看到内联函数在编译时会将函数体直接插入到调用处,避免了函数调用的开销。
需要注意的是,编译器并不一定会将addInline
函数真正内联处理,它会根据具体情况进行优化决策。如果函数体比较复杂或者有其他不适合内联的情况,编译器可能会选择不进行内联。
(七)auto 关键字(C++11)
在 C++11 中,auto关键字用于自动类型推导。它可以根据初始化表达式的类型自动推断出变量的类型。
👉例如:auto i = 10;,这里i的类型会被自动推导为int。
auto不能推导的情况包括:
- 函数参数的类型不能用auto推导。
- 数组的类型不能用auto推导,但是可以用auto推导数组的元素类型,然后结合std::begin和std::end来遍历数组。
🔥代码解释:
#include <iostream>
#include <vector>
#include <algorithm>
// 正常使用 auto 进行类型推导
void normalAutoUsage() {
auto i = 10;
std::cout << "Type of i is deduced as int. Value of i: " << i << std::endl;
auto str = "Hello, world!";
std::cout << "Type of str is deduced as const char*. Value of str: " << str << std::endl;
}
// 函数参数不能用 auto 推导
void cannotDeduceFunctionParams(auto a) {
// 错误,不能用 auto 推导函数参数类型
}
// 数组的类型不能用 auto 推导,但可以推导元素类型并遍历
void arrayDeduction() {
int arr[] = {1, 2, 3, 4, 5};
// auto arr2 = arr; // 错误,不能用 auto 推导数组类型
for (auto element : arr) {
std::cout << "Array element: " << element << std::endl;
}
}
int main() {
normalAutoUsage();
// cannotDeduceFunctionParams(10); // 会编译错误
arrayDeduction();
return 0;
}
(八)基于范围的 for 循环(C++11)
在 C++11 中,基于范围的 for 循环提供了一种更简洁的方式来遍历容器或数组。
传统的 for 循环遍历数组(C++98):
int arr[] = {1, 2, 3, 4, 5};
int len = sizeof(arr) / sizeof(arr[0]);
std::cout << "传统 for 循环遍历数组:";
for (int i = 0; i < len; i++) {
std::cout << arr[i] << " ";
}
std::cout << std::endl;
基于范围的 for 循环遍历数组(C++11):
int arr[] = {1, 2, 3, 4, 5};
std::cout << "基于范围的 for 循环遍历数组:";
for (int& x : arr) {
std::cout << x << " ";
}
std::cout << std::endl;
for (int& x : arr)
:这是 C++11 中基于范围的 for 循环语法。在这里,arr
是要遍历的数组。int& x
表示定义了一个引用类型的变量 x
,它会依次引用数组中的每个元素。使用引用的好处是可以在循环体内修改数组元素的值,如果只是读取元素的值,也可以使用 const int& x
或者 int x
。
(九)指针空值 nullptr
在 C++11 中,引入了nullptr作为指针的空值。与 C++98 中的指针空值(如NULL或 0)相比,nullptr具有更好的类型安全性。
⭐nullptr可以明确表示指针为空,避免了与整数类型的混淆。
👇以下是用 C++ 代码解释 C++11 中的指针空值 nullptr
:
#include <iostream>
void func(int num) {
std::cout << "Called function with integer parameter: " << num << std::endl;
}
void func(int* ptr) {
if (ptr == nullptr) {
std::cout << "Called function with pointer parameter (nullptr)." << std::endl;
} else {
std::cout << "Called function with pointer parameter (non-null): " << *ptr << std::endl;
}
}
int main() {
// 使用 nullptr 调用指针参数的函数
func(nullptr);
int num = 10;
// 使用整数地址调用指针参数的函数
func(&num);
// 使用 0 调用可能产生歧义
func(0);
return 0;
}
在这个例子中:
- 定义了两个重载的函数
func
,一个接受整数参数,另一个接受指针参数。- 在
main
函数中,分别使用nullptr
、整数变量的地址和0
来调用func
函数。- 使用
nullptr
可以明确地表示调用指针参数的函数,并且不会产生歧义。而使用0
可能会导致调用歧义,因为编译器可能不确定是调用整数参数的函数还是指针参数的函数(在某些情况下,0
可能被视为整数常量,也可能被视为空指针常量)。
通过这个例子可以看出,nullptr
在表示指针空值时具有更好的类型安全性。
💯C++资源推荐
1.在线课程:
- C++ 程序设计北京大学:由刘家瑛教授和郭炜教授授课,面向已经掌握 C 语言的学员,将掌握 C++ 语言中的类、对象、运算符重载、继承、多态等面向对象的机制,以及模版、STL 等泛型程序设计的机制。本课程为期 4 周,每周 8 - 10 小时。
- Fundamentals of C++ IBM 公司:由 Sathya Ponmalar 等授课,将开启你的 C++ 程序员之旅,通过许多自动评估的 C++ 编码活动,帮助你理解 C++ 的语法和语义,并建立强大的编程和解决问题的技能。本课程为期 5 周,每周 5 - 6 小时。
- C++ 程序设计西北工业大学:由魏英教授授课,使学员能够使用一种开发工具熟练的进行软件开发,为学员将来的创新实验、毕业设计、科学研究提供有力的技术支持。本课程为期 20 周,每周 6 小时。
- C++ Programming: Basic Skills Codio 公司:由 Anh Le 授课,教你用 C++ 写一个简单程序所需的基本技能。课程是为没有编码经验的学习者设计的,本课程为期 5 周,每周 2 - 3 小时。
- Introduction to C++ 微软公司:由 Gerry O'Brien 等授课,介绍 C++ 语言,课程由四个模块组成:C++ 语法、C++ 语言基础、如何在 C++ 中创建函数、为微软公司后续中级和高级 C++ 课程做好准备。本课程为期 4 周,每周 3 - 5 个小时。
- Object-Oriented Data Structures in C++ 伊利诺伊大学香槟分校:由 Wade Fagen-Ulmschneider 教授授课,将教你如何用 C++ 语言编写程序,包括如何建立一个编写和调试 C++ 代码的开发环境,以及如何将数据结构实现为 C++ 分类。本课程为期 4 周,每周 3 - 7 个小时。
2.官方文档:C++ 的官方文档提供了最准确、最全面的语言规范和指南,是学习与参考的宝贵资源。
3.社区论坛:
c++ 博客(A Charmer)
Stack Overflow:解决编程问题的知名社区,在 C++ 编程方面有大量的问题和解答。
Reddit:参与 C++ 社区讨论,获取最新的 C++ 资讯和学习资源。
以上就是本文的全部内容了!
📣如果你想深入了解 C++,欢迎持续关注我【A Charmer】,我将为你带来更多精彩的编程知识分享。😉