C++学习记录

由于之前写C/C++程序时,动不动就容易报错,也就没继续用,一直觉得C++很难。最近在学习机器学习时,用到了一个C++神经网络库,并且在菜鸟教程中看到了C++的教程,于是学了一下,发现根本没有想象中的那么难,可能也是这个教程讲的比较好吧。

现在把一些学到的比较重要的点记录一下吧!

1. ifndef

突然觉得在头文件开头加一个ifndef的宏定义是那么重要,不然在多个cpp文件include这同一个头文件时会出现声明冲突的错误。以Animal.h为例,它的用法是:

#ifndef _ANIMAL_H_
#define _ANIMAL_H_
//代码写在这里

#endif

2. static与extern

static 存储类:指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
extern 存储类:当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
详见C++存储类

3. 析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。但是我在阅读cnn库的源码时,发现了作者对析构函数的妙用,让我感叹其精妙之处。这段代码的作用是输出每次循环或者任务的用时,代码如下:

#ifndef _TIMING_H_
#define _TIMING_H_

#include 
#include 
#include 

namespace cnn {

struct Timer {
    Timer(const std::string& msg) : msg(msg), start(std::chrono::high_resolution_clock::now()) {}
    ~Timer() {
        auto stop = std::chrono::high_resolution_clock::now();
        std::cerr << '[' << msg << ' ' << std::chrono::duration<double, std::milli>(stop-start).count() << " ms]\n";
  }
    std::string msg;
    std::chrono::high_resolution_clock::time_point start;
};

} // namespace cnn

#endif

在使用时,只需要在循环开始的地方声明一个改结构的对象,如:Timer iteration("completed in")。创建该对象时传入提示信息并记录开始时间,而当该次循环结束时,对象自动销毁,并调用析构函数,在析构函数中获取结束时间,计算时间差并输出。

4. 友元函数与内联函数

friend:类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
inline:C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。注:在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

5. 继承

class derived-class: access-specifier base-class

access-specifier有public,protected和private:

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

  • 保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。

  • 私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。

6. 多态/虚函数

当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。

  • 早绑定:当基类函数没有virtual关键字时,子类调用函数会被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为函数在程序编译期间就已经设置好了。
  • 后期绑定:当基类函数前面添加virtual关键字时,派生类中重新定义基类中定义的虚函数,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
  • 纯虚函数:当函数在基类中没有意义,无法给出实现时,可以使用纯虚函数。例如求面积的函数 area() 在基类Shape中无法给出实现,此时可以使用纯虚函数,即
 virtual int area() = 0;

= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。有纯虚函数的类称为抽象类,抽象类不能被用于实例化对象,它只能作为接口使用。如果试图实例化一个抽象类的对象,会导致编译错误。

注:

  • 虚函数为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数。
  • 纯虚函数在基类中是没有定义的,必须在子类中加以实现。

7. 其他

  • 命名空间(namespace):为了预防命名冲突
  • 模板(template):用于创建泛型类或函数
  • 重载与重写
    • 重载(overload):同一可访问区内被声明的几个具有不同参数列(参数的类型,个数,顺序不同)的同名函数,根据参数列表确定调用哪个函数,重载不关心函数返回类型。
    • 重写(override):派生类中存在重新定义的函数。其函数名,参数列表,返回值类型,所有都必须同基类中被重写的函数一致。重写的基类中被重写的函数必须有virtual修饰。
    • 详见C++中重载、重写(覆盖)和隐藏的区别实例分析
  • override:在C++11中为了帮助程序员写继承结构复杂的类型,引入了虚函数描述符override,如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。该关键字可以防止在子类中函数被写错。

参考资料

  1. C++教程 | 菜鸟教程

你可能感兴趣的:(C/C++)