C++学习笔记

前言

必修课系统教学的只有C语言和Java,而Python比较简单虽没有系统教学但也在许多课程中经常使用。本来培养方案上是有C++的选修课的,但是不知道为什么不开了,上学期的编译也有用C++编程的选择。笔者这个篇笔记只记录学习其与C/Java的不同点。

参考:[菜鸟教程](C++ 教程 | 菜鸟教程 (runoob.com))

1、简介

C++ 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。

C++ 被认为是一种中级语言,它综合了高级语言和低级语言的特点。

1.1、特点

  • C++有类似Java的面向对象的特点,即封装、继承、多态、抽象(在学Java的时候就已经知道,Java只能单继承而C++可以多继承)。
  • 标准的 C++ 由三个重要部分组成:核心语言、C++标准库、标准模板库STL(这个在算法课上用过)

1.2、使用

C++的使用场景(后续若参加冯如杯,需要结合产业实际,如老师说的汽车C++软件):

  • 游戏开发:C++ 是游戏开发领域中最常用的编程语言之一,因为它具有高效的性能和直接控制硬件的能力。许多主要的游戏引擎,如 Unreal Engine 和 Unity,都使用 C++ 编写。

    我高中/大一的时候还很想从事游戏开发呢……

  • 嵌入式系统开发:C++ 可以在嵌入式系统中发挥重要作用,如智能手机、汽车、机器人和家电等领域。由于嵌入式系统通常具有严格的资源限制和实时要求,因此 C++ 的高效性能和内存控制功能非常有用。

  • 金融领域:C++ 在金融领域中被广泛应用,如高频交易、算法交易和风险管理等领域。由于这些应用程序需要高效的性能和对硬件的直接控制,C++ 语言是一个合适的选择。

  • 图形图像处理:C++ 可以用于开发图形和图像处理应用程序,如计算机视觉、计算机图形学和人工智能领域。由于这些应用程序需要高效的计算能力和对硬件的控制,因此 C++ 是一个很好的选择。

    智能计算系统的C++算子?

  • 科学计算和数值分析:C++ 可以用于开发科学计算和数值分析应用程序,如数值模拟和高性能计算等领域。由于这些应用程序需要高效的计算能力和对硬件的直接控制,C++ 语言是一个很好的选择。

2、基本语法

过程性语言部分,与C/Java类似。

3、数据类型

  • 基本类型:相较于C语言多了bool类

  • 枚举类型:类似Java的枚举类

    1
    2
    3
    4
    5
    6
    enum 枚举名{ 
    标识符[=整型常数],
    标识符[=整型常数],
    ...
    标识符[=整型常数]
    } 枚举变量;
  • 类型转换:

    • 静态转换(static_cast):将一种数据类型的值强制转换为另一种数据类型的值。不进行任何运行时类型检查,因此可能会导致运行时错误。

      1
      2
      int i = 10;
      float f = static_cast<float>(i); // 静态将int类型转换为float类型
    • 动态转换(dynamic_cast):通常用于将一个基类指针或引用转换为派生类指针或引用。运行时进行类型检查,如果不能进行转换则返回空指针或引发异常。

      1
      2
      3
      4
      class Base {};
      class Derived : public Base {};
      Base* ptr_base = new Derived;
      Derived* ptr_derived = dynamic_cast<Derived*>(ptr_base); // 将基类指针转换为派生类指针
    • 常量转换(const_cast):用于将 const 类型的对象转换为非 const 类型的对象。只能用于转换掉 const 属性,不能改变对象的类型。

    • 重新解释转换(reinterpret_cast):重新解释转换将一个数据类型的值重新解释为另一个数据类型的值,通常用于在不同的数据类型之间进行转换。不进行任何类型检查。

4、修饰符类型

类型限定符提供了变量的额外信息,用于在定义变量或函数时改变它们的默认行为的关键字。

限定符 含义
const const 定义常量,表示该变量的值不能被修改。
volatile 修饰符 volatile 告诉该变量的值可能会被程序以外的因素改变,如硬件或其他线程。。
restrict restrict 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。
mutable mutable 用于修饰类的成员变量。被 mutable 修饰的成员变量可以被修改,即使它们所在的对象是 const 的。
static 用于定义静态变量,表示该变量的作用域仅限于当前文件或当前函数内,不会被其他文件或函数访问。
register 用于定义寄存器变量,表示该变量被频繁使用,可以存储在CPU的寄存器中,以提高程序的运行效率。
  • mutable实例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Example {
    public:
    int get_value() const {
    return value_; // const 关键字表示该成员函数不会修改对象中的数据成员
    }
    void set_value(int value) const {
    value_ = value; // mutable 关键字允许在 const 成员函数中修改成员变量
    }
    private:
    mutable int value_;
    };

5、存储类

存储类定义 C++ 程序中变量/函数的范围(可见性)和生命周期。这些说明符放置在它们所修饰的类型之前。包括auto、register(弃用)、static、extern、mutable、thread_local。

auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

1
2
3
4
auto f=3.14;      //double
auto s("hello"); //const char*
auto z = new auto(9); // int*
auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型

6、函数

6.1、参数默认值

与C/Java不同而与Python类似:定义一个函数时,可以为参数列表中后边的每一个参数指定默认值;当调用函数时,如果实际参数的值留空,则使用这个默认值。

6.2、Lambda函数/表达式

表达式及例子如下:

1
2
3
4
5
6
[capture](parameters)->return-type{body}	// 有返回值
[capture](parameters){body} // 无返回值

[](int x, int y){ return x < y ; }
[]{ ++global_x; }
[](int x, int y) -> int { int z = x + y; return z + x; }

[]指定了该表达式可以访问的当前作用域的变量(包括引用和传值传递两种形式)。

7、字符串

C++ 提供了以下两种类型的字符串表示形式:

  • C 风格字符串(与C语言用法一致)
  • C++ 引入的 string 类类型(相当于Java中一个String类的对象)

8、引用

引用变量是一个别名,也就是说,它是某个已存在变量的另一个名字。一旦把引用初始化为某个变量,就可以使用该引用名称或变量名称来指向变量。

8.1、引用 VS 指针

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

8.2、创建引用

1
int i = 17;

为 i 声明引用变量如下:

1
2
int&  r = i;
double& s = d;

在这些声明中,& 读作引用

至于C++的输入输出、vector、数据结构等在算法课已经熟悉,就不记了。

9、类&对象

与C/Java区别最大的特点之一。

  • 定义方法:

img

  • 类成员函数定义:可在类中定义,也可在类的外部使用范围解析运算符 :: 定义。

  • 类访问修饰符:由于不分包,没有Java的默认(包访问权限),默认情况下是定义为 private。

  • 构造函数:与Java类似,函数名为类名且无返回值类型,但可以使用初始化列表来初始化字段:

    1
    2
    3
    4
    C::C( double a, double b, double c): X(a), Y(b), Z(c) // 对成员X, Y, Z分别赋值a, b, c
    {
    ....
    }
  • 类的析构函数:是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。

  • 拷贝构造函数:是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数(实现深拷贝)。形式:

    1
    2
    3
    classname (const classname &obj) {
    // 拷贝构造函数的主体
    }
  • 类的友元函数/友元类:定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。

  • 其他与C/Java类似的:内联函数、this指针、静态成员变量及函数

10、继承

  • 与Java不同,C++中一个类可以派生自多个类,可以从多个基类继承数据和函数。多继承使用逗号隔开。

  • 定义一个派生类,我们使用一个类派生列表来指定基类。类派生列表以一个或多个基类命名,形式如下:

    1
    class derived-class: access-specifier base-class

    我们几乎不使用 protectedprivate 继承,通常使用 public 继承。当使用不同类型的继承时,遵循以下几个规则:

    • 公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
    • 保护继承(protected): 当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
    • 私有继承(private):当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

    与Java不同,Java子类不能缩小父类成员的可见性。

11、重载运算符和重载函数

函数重载与Java类似,这里着重记录运算符重载。

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。

1
Box operator+(const Box&);

声明加法运算符用于把两个 Box 对象相加,返回最终的 Box 对象。大多数的重载运算符可被定义为普通的非成员函数或者被定义为类成员函数。如果我们定义上面的函数为类的非成员函数,那么我们需要为每次操作传递两个参数,如下所示:

1
Box operator+(const Box&, const Box&);

12、多态

与Java的多态类似。

  • 虚函数:使用关键字virtual,子类重写要加override。(可以在基类中提供默认实现)
  • 纯虚函数(Pure Virtual Functions):
    • 一个包含纯虚函数的类被称为抽象类(Abstract Class),它不能被直接实例化。(与Java中的抽象类类似,是C++中的接口)
    • 纯虚函数没有函数体,声明时使用= 0。(与Java的抽象方法类似)

13、其他特性

  • 动态内存:使用new分配堆空间(与Java类似),使用delete释放内存(Java自动垃圾回收机制)

    对于分配对象空间,C++支持栈分配内存和堆分配内存(需要显示释放),而Java是完全基于堆的。

  • 命名空间:作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。可以嵌套。使用**范围解析运算符 ::**调用带有命名空间的函数或变量。

  • 模板:类似Java的泛型。

    • 函数模板:(与Java泛型不同,使用时不指定的类型,直接使用)

      1
      2
      3
      4
      template <typename type> ret-type func-name(parameter list)
      {
      // 函数的主体
      }
    • 类模板:(需要指定类型)

      1
      2
      3
      4
      5
      6
      template <class type> class class-name {
      .
      .
      .
      }

  • 导入标准库、预处理器:与C语言相同。