C++入门基础

1. C++的发展历程

C++的诞生可以追溯到1979年,由Bjarne Stroustrup(本贾尼·斯特劳斯特卢普)在贝尔实验室初步构想。当时,他深感现有语言(如C语言)在处理复杂软件开发项目,尤其是模拟和操作系统开发方面的局限性。

1983年,Stroustrup在C语言的基础上融入了面向对象编程的特性,设计出了C++的雏形,引入了类、封装、继承等核心概念,并正式命名为C++。

此后,C++在学术界和工业界迅速普及,其标准库和模板等特性也得到持续发展。C++的标准化工作始于1989年,由ANSI和ISO(国际标准化组织)联合委员会负责。1994年,委员会发布了首个标准化草案。随后,由Alexander Stepanov、Meng Lee和David R Musser在惠普实验室开发的STL(标准模板库)也被纳入C++标准。尽管这扩展了C++的原始定义并延迟了标准化进程,但最终在1997年11月14日通过了最终草案,并于1998年正式投入使用。

1.1 C++版本迭代

C++经历了多次版本更新,主要版本及其特性如下表所示:

时间阶段内容
1998年C++98C++官方第一个版本,绝大多数编译器支持,获得了ISO认可,以模板方式重写C++标准库,引入了STL。
2003年C++03主要修订C++98标准中的错误和漏洞,引入了tr1库等新特性和功能。
2011年C++11革命性更新,增加了大量新特性,如lambda表达式、范围for循环、右值引用和移动语义、智能指针、标准线程库等。
2014年C++14对C++11的扩展和改进,例如泛型的lambda表达式、函数返回值类型推导、二进制字面常量等。
2017年C++17进一步增强功能和表达能力,引入if constexpr、折叠表达式等语法特性,改进了string、filesystem等标准库组件。
2020年C++20又一个重要里程碑,引入协程(Coroutines)、概念(Concepts)、模块化(Modules)等。
2023年C++23小版本更新,完善现有特性,增加了if consteval、flat_map, import std导入标准库等。
2026年C++26制定中。

1.2 关于C++23网络库的小插曲

C++23原计划引入网络库(networking),但最终发布时并未包含,引发了社区的广泛讨论。据称,Asio库的标准化进程受到了一些阻碍。尽管如此,C++标准委员会(WG21)对网络功能的重视程度依然很高,未来版本仍值得期待。


2. C++参考文档

学习C++时,可以参考以下文档:

建议结合使用这些文档,各有优势。


3. C++的重要性

3.1 编程语言排行榜

根据TIOBE排行榜(2024年6月数据),C++在热门编程语言中位居第二。TIOBE排行榜反映了编程语言的受欢迎程度,基于程序员数量、课程和第三方厂商数量,并结合搜索引擎数据统计得出。

3.2 C++在工作领域中的应用

C++的应用领域非常广泛,包括但不限于:

  • 大型系统软件开发:如编译器、数据库、操作系统、浏览器等。
  • 音视频处理:许多主流的音视频开源库(如FFmpeg、WebRTC)主要技术栈为C++。
  • PC客户端开发:例如使用C++和QT开发Windows桌面应用。
  • 服务端开发:用于对性能要求较高的场景,如游戏服务、流媒体服务、量化高频交易服务等。
  • 游戏引擎开发:许多知名的游戏引擎(如UE4、Cocos2d-x)均使用C++开发。
  • 嵌入式开发:应用于智能手环、摄像头、车载系统等设备的软件控制。
  • 机器学习引擎:机器学习底层的许多算法使用C++实现,上层用Python封装。
  • 测试开发/测试:编写自动化测试脚本、性能测试以及开发测试工具等。

4. C++学习建议和书籍推荐

4.1 C++学习难度

C++是一门相对较难学习和精通的语言,学习曲线较为陡峭。这既有历史包袱的原因,也与语言本身的设计和发展历程有关。

4.2 C++学习建议

  • 课后务必练习课堂上讲过的所有样例,深入理解相关知识点。
  • 如果时间允许,最好每节课后总结博客或笔记;时间紧张时,至少要整理重点章节的笔记。

4.3 学习书籍推荐

  • 《C++ Primer》:经典的C++语法书籍,内容详尽,适合初学预习及后续查阅。
  • 《STL源码剖析》:从底层实现角度深入剖析STL,有助于学习高效简洁的数据结构和算法代码实现及泛型封装。建议在课程学习过半后阅读。
  • 《Effective C++》:总结了55个编写高效、正确C++代码的实用条款。建议在中后期学习,并在工作1-2年后重读,会有新的收获。

5. C++的第一个程序

C++兼容C语言的大部分语法,因此C语言的 “hello world” 程序在C++中依然可以运行,只需将文件后缀改为 .cpp。在Linux环境下,需要使用 g++ 编译器进行编译。

C语言风格的 “hello world”:

// test.cpp
#include<stdio.h>

int main()
{
    printf("hello world\n");
    return 0;
}

更规范的C++版本 “hello world”:

// test.cpp
#include<iostream>
using namespace std;

int main()
{
    cout << "hello world\n" << endl;
    return 0;
}

6. 命名空间 (Namespace)

6.1 namespace的价值

在C/C++中,大量的变量、函数和类名都存在于全局作用域,容易导致命名冲突或名字污染。namespace 关键字的出现就是为了解决这个问题,它通过对标识符的名称进行本地化来避免命名冲突。例如,C语言中 rand 函数与同名全局变量的冲突可以通过命名空间解决。

6.2 namespace的定义

  • 定义命名空间使用 namespace 关键字,后跟命名空间名称,再接一对花括号 {},括号内即为命名空间的成员(变量、函数、类型等)。
  • namespace 本质上是定义一个独立的域,与全局域隔离,不同域中可以定义同名成员。
  • C++中的域包括函数局部域、全局域、命名空间域和类域。域主要影响编译时对变量/函数/类型出处的查找逻辑,从而解决名字冲突。局部域和全局域还会影响变量的生命周期,而命名空间域和类域不影响生命周期。
  • namespace 可以嵌套定义。项目中多个文件定义的同名 namespace 会被视为同一个 namespace,不会冲突。
  • C++标准库的所有内容都放在名为 std (standard) 的命名空间中。

示例:

#include <stdio.h>
#include <stdlib.h>

namespace bit
{
    int rand = 10;
    int Add(int left, int right)
    {
        return left + right;
    }
    struct Node
    {
        struct Node* next;
        int val;
    };
}

int main()
{
    printf("%p\n", rand); // 访问全局的rand函数指针
    printf("%d\n", bit::rand); // 指定访问bit命名空间中的rand
    return 0;
}

嵌套命名空间示例:

namespace bit
{
    namespace pg
    {
        int rand = 1;
        // ...
    }
    namespace hg
    {
        int rand = 2;
        // ...
    }
}

int main()
{
    printf("%d\n", bit::pg::rand);
    printf("%d\n", bit::hg::rand);
    return 0;
}

6.3 命名空间使用

编译器默认只在局部或全局查找变量的声明/定义,不会进入命名空间查找。因此,要使用命名空间中定义的成员,有三种方式:

  1. 指定命名空间访问:使用作用域解析运算符 ::,例如 namespace_name::member_name。项目中推荐此方式。
  2. using 将命名空间中某个成员展开:例如 using namespace_name::member_name;。项目中对于经常访问且不存在冲突的成员推荐此方式。
  3. 展开命名空间中全部成员:例如 using namespace namespace_name;。项目中不推荐,因为冲突风险大,但日常小型练习程序为了方便可以使用。

7. C++ 输入&输出

  • <iostream> 是C++标准的输入输出流库,定义了标准输入输出对象。
  • std::cinistream 类的对象,用于标准输入流(窄字符)。
  • std::coutostream 类的对象,用于标准输出流(窄字符)。
  • std::endl 是一个函数,在流插入输出时,相当于插入一个换行符并刷新缓冲区。
  • << 是流插入运算符,>> 是流提取运算符。
  • C++的输入输出比C语言的 printf/scanf 更方便,能自动识别变量类型(通过函数重载实现),并且更好地支持自定义类型对象的输入输出。
  • 日常练习中可以使用 using namespace std;,但在实际项目中不建议。

示例:

#include <iostream>
using namespace std;

int main()
{
    int a = 0;
    double b = 0.1;
    char c = 'x';

    cout << a << " " << b << " " << c << endl;
    cin >> a >> b >> c;
    cout << a << " " << b << endl;
    return 0;
}

提高IO效率的优化代码:

ios_base::sync_with_stdio(false); // 关闭与C标准库的同步
cin.tie(nullptr);                 // 解除cin与cout的绑定
cout.tie(nullptr);

8. 缺省参数

缺省参数(也叫默认参数)是在声明或定义函数时为函数的参数指定一个默认值。调用该函数时,如果未指定实参,则采用该形参的默认值;否则,使用指定的实参。

  • 全缺省参数:函数的所有形参都带有默认值。
  • 半缺省参数:函数的部分形参带有默认值。C++规定半缺省参数必须从右往左依次连续给出,不能跳跃。
  • 调用带缺省参数的函数时,实参必须从左到右依次给出,不能跳跃。
  • 当函数声明和定义分离时,缺省参数不能同时出现在声明和定义中,规定必须在函数声明中给出默认值。

示例:

#include <iostream>
using namespace std;

// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl << endl;
}

// 半缺省
void Func2(int a, int b = 10, int c = 20)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl << endl;
}

int main()
{
    Func1();       // 输出 10, 20, 30
    Func1(1);      // 输出 1, 20, 30
    Func2(100);    // 输出 100, 10, 20
    Func2(100, 200); // 输出 100, 200, 20
    return 0;
}

9. 函数重载

C++支持在同一作用域中出现同名函数,但要求这些同名函数的形参列表不同(参数个数、类型或顺序不同)。这使得函数调用表现出多态行为,使用更灵活。

  • 参数类型不同
  • 参数个数不同
  • 参数类型顺序不同

注意:函数的返回值类型不同不能作为函数重载的条件,因为调用时无法区分。如果两个函数仅因缺省参数而可能导致调用歧义,编译器会报错。

示例:

#include<iostream>
using namespace std;

// 参数类型不同
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;
}

// 参数个数不同
void f()
{
    cout << "f()" << endl;
}

void f(int a)
{
    cout << "f(int a)" << endl;
}

int main()
{
    Add(10, 20);    // 调用int版本
    Add(10.1, 20.2);// 调用double版本
    f();            // 调用无参版本
    f(10);          // 调用有参版本
    return 0;
}

10. 引用 (Reference)

10.1 引用的概念和定义

引用不是新定义一个变量,而是给已存在的变量取一个别名。编译器不会为引用变量开辟内存空间,它和它引用的变量共享同一块内存空间。

示例:

#include<iostream>
using namespace std;

int main()
{
    int a = 0;
    int& b = a; // b是a的别名
    int& c = a; // c也是a的别名
    ++b;
    cout << a << endl; // 输出1
    return 0;
}

10.2 引用的特性

  • 引用在定义时必须初始化。
  • 一个变量可以有多个引用。
  • 引用一旦引用一个实体,就不能再引用其他实体。对引用赋值,实际上是修改其引用的实体的值。

10.3 引用的使用

引用主要用于:

  • 引用传参:功能类似指针传参,但使用更方便,可以减少拷贝,提高效率,并能修改传入的参数。
  • 引用做返回值:场景相对复杂,可以减少拷贝,提高效率,有时可以直接修改函数返回的变量。

示例 - 引用传参:

void Swap(int& rx, int& ry)
{
    int tmp = rx;
    rx = ry;
    ry = tmp;
}

10.4 const引用

  • 可以引用一个 const 对象,但必须使用 const 引用。
  • const 引用也可以引用普通对象,此时引用的访问权限被缩小(不能通过该引用修改对象)。
  • 当引用一个表达式的结果或发生类型转换时产生的临时对象时,必须使用 const 引用。

示例:

const int a = 10;
const int& ra = a; // 正确

int b = 20;
const int& rb = b; // 正确,权限缩小

11. inline 内联函数

  • inline 修饰的函数称为内联函数。编译器在调用内联函数的地方会尝试将其展开,避免函数调用的栈帧开销,从而提高效率。
  • 通常适用于频繁调用且短小的函数。递归函数或代码量较大的函数即使加了 inline 也可能被忽略。
  • inline 函数不建议声明和定义分离到不同文件,否则可能导致链接错误。

示例:

#include<iostream>
using namespace std;

inline int Add(int x, int y)
{
    return x + y;
}

int main()
{
    cout << Add(1, 2) << endl;
    return 0;
}

12. nullptr

  • C++11引入了 nullptr 关键字,它是一种特殊类型的字面量,可以转换成任意其他类型的指针类型,但不能被隐式转换为整数类型。
  • 使用 nullptr 定义空指针可以避免因 NULL 的宏定义带来的类型转换问题。

示例:

#include<iostream>
using namespace std;

void f(int x)
{
    cout << "f(int x)" << endl;
}

void f(int* ptr)
{
    cout << "f(int* ptr)" << endl;
}

int main()
{
    f(0);        // 调用 f(int x)
    f(nullptr);  // 调用 f(int* ptr)
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值