Review C++

这辈子都不可能精通C++了

Posted by tianchen on November 26, 2019

C++

  • 从来没有系统学习过C++,导致用起来很费劲,开始做一个积累记录
  • C++是
    • 静态类型语言:在编译时进行类型检查,而不是在执行时
    • OOP:封装,抽象,继承,多态
    • 包含: 核心语言,标准库(string.h),标准模板库(STL)

特性

  • 类型转换更安全:C是允许隐式类型转换的,short和int可以互相赋值,C++没有禁止,但是会给警告(仅在int到short的时候(可能会丢失数据))
  • inline(内联函数):编译在调用内联函数的时候,直接插入代码,而不是调用(防止宏定义可能多次)频繁调用一些短函数会让性能下降
    • inline int min(int x, int y)
  • 动态创建对象
    • C
      struct T* t = (struct T*) malloc(sizeof(*t));
      init(t);
      destroy(t);
      free(t);
      
    • C++中的malloc和free只管分配内存,并不会调用构造和析构函数
      T* t = new T(param1, param2); // 分配内存,并调用 T 的构造函数
      delete t;     // 调用 T 的析构函数,并释放内存
      
    • 对内置类型也可以用new 和delete
      • 总之不是很优雅不建议运用
  • 函数重载 - 非常直观,当然只能重载参数,不能重载返回值
    std::string s("hello world"); // ok. s = "hello world"
    std::string s(5, 'a');        // ok. s = "aaaaa"
    
    • 重载运算符(针对某种对象)
  • 引用 (&
    • 由于操作符重载不允许传指针,需要引用
    • 思考一下:
      • 当定义int x = 3的时候,编译器会申请4字节的内存,将这块内存的值设置为3,然后将变量名字与这块内存的地址绑定起来
        • 变量的本质式一块内存,编译器做的是将那块内存和变量给绑定起来
      • 指针的本质
        • 代码int* p = &x在64位的机器上,编译器申请了一块8个字节的内存,将内存的值设置为变量x的地址,将名字p绑定上
        • int*也是一种类
      • 与指针的区别
        • 本质上引用就是常量指针(指向的ADDR)
        • 指针本身允许寻址&p表示了指针常量本身的地址,被引用对象用*p表示
        • 引用变量本身不允许寻址 &r返回的是被引用对象的地址(这么看起来差了一阶)
        • 只有C++有引用,语法更简单
        • 应用的本身是一个 对象
        • 指针的应用范围比引用更大,但是写法更繁琐
        • 数组元素是指针,不可以是引用
          • 很直观a[0] = 8指的是将数组中0位置的值设置为8,还是将array[0]所引用的对象设置为8呢?
        • 不能空引用,可以空指针
        • 使用引用的时候不需要解引用
        • 有人说引用就像是指针的语法糖hhh
  • 拷贝构造函数 Apple(const Apple& New_Apple)
    • 可以使用#define DISALLOW_COPY_AND_ASSIGN(Type) \Type(const Type&); \void operator=(const Type&)的宏定义
    • 然后DISALLOW_COPY_AND_ASSIGN(Apple);以禁用拷贝构造
  • 模板 C++的标准模板库STL
    • 函数模板 一个函数支持多种类型
      template <typename T>
      inline T add(T x, T y{
        return x+y
      })
      
    • 对于上面的例子,用int调用会编译出int版本的函数,double同理
    • 类模板 (C++11 std中unique pointer的实现代码)用到了类模板,重载与
      template <typename T>
      class unique_ptr {
      public:
        unique_ptr(T* ptr = 0) {  // 提供默认参数 0
            _ptr = ptr;
        }
      
        ~unique_ptr() {
            delete _ptr; // 释放内存,delete NULL 不会有问题
        }
      
        T* get() const { // const 成员函数表示不修改类中的数据,仅此而已
            return _ptr;
        }
      
        // 重载操作符 -> 与 *,使得 unique_ptr 的行为更像指针
        T* operator->() const {
            //assert(_ptr != 0);
            return _ptr;
        }
      
        T& operator*() const {
            //assert(_ptr != 0);
            return *_ptr;
        }
      
        void reset(T* ptr = 0) {
            delete _ptr;
            ptr = _ptr;
        }
      
        T* release() {  // 交出指针所有权
            T* ptr = _ptr;
            _ptr = 0;
            return ptr;
        }
            
      private:
        T* _ptr;
        //DISALLOW_COPY_AND_ASSIGN(unique_ptr);
      };
      
    • 使用方式 ``` c++ unique_ptr pi(new int(7)); std::cout << "*pi = " << *pi << std::endl; // *pi 等价于 pi.operator*()

    unique_ptr pa(new Apple); pa->turn_red(); // 假设 Apple 类有一个 turn_red 方法 pa.operator->()->turn_red(); // 等价于上面的调用方式 ```

  • namespace(命名空间)
    • 可以把不同的全局变量放到不同的namespace当中
    • C++标准所提供的对象都在命名空间std中
    • 如果没有using namespace std那么就std::cout
  • 继承有单继承,多继承,还有virtual继承(和基函数没啥关系)
    • 很少遇到virtual的
  • 虚函数(Virtual Function)
    • 目的: 为了解决基类指针(或者是引用)调用子类方法问题
    • Example
      # include <iostream>
      
      class A{
          public:
              void fun(){
                  std::cout << "A::fun() is Called" << std::endl;
              }
      }
      
      class B : public A{
          public:
              void fun(){
                  std::cout << "B::fun() is Called" << std::endl;
              }
      }
      
      int main(){
          B b;
          A* pa = &b;
          A& ra = b;
          pa->fun();
          ra.fun();
          return 0;
      }
      
      • 最后的输出都是A的被调用了,函数调用在编译的时候绑定到了基类中的版本,而不会用派生类中的
      • 而如果把fun在A类中设置为虚函数,那么就可以调用B中的fun(即使在B中也是虚函数),由于编译器采用了动态绑定,A类指针实际引用的是B对象
      • 带有虚函数的类,析构函数一般也要设置为虚
  • 纯虚函数
    • virtual void fun() = 0;含有纯虚函数的类,不能被实例化,叫做抽象类
      • 其作用是对外提供统一的抽象接口,由派生来来实现

etc

  • typedef int new_int; new_int i;
  • 枚举
    • 默认第一个为0,之后1,2,3,也可以从中间开始指定,之后的比前面的多1
       enum color{
       red=1;
       green=2,
       blue=3
       };
      
  • 作用域
    • 全局(Global)
    • 局部(函数或者是代码块中)
      • 定义局部变量时,系统不会自动初始化(全局会自动初始化,大部分值为0)
    • 形参,函数定义时
  • 常量可以加后缀(u l)表示unisgned和long
    • 3.1415E-3L 科学计数法的常量
    • 定义常量
      • 可以采用 预处理器 #define
      • 或者是const
        • 这里const实际是一种修饰符
        修饰符 意义
        const 运行时不能被改变
        volatile 告诉编译器不优化该变量的存储(从内存中转移到寄存器中)程序可以直接从内存中读取变量)
        restrict 具有该修饰的指针是唯一指向对象的方式(Only In C99)
    • 一般要把常量定义成大写
  • 存储类型(决定了变量的生命周期)
存储类 特点
register 局部变量存储在reg中而不是ram中(不能对他进行一元运算(因为他不不在内存中))
static 1. 局部变量:在程序的执行周期之内保持该局部变量的值,不用每次离开作用域的时候销毁,可以在函数调用之间保持值
  2. 全局变量,作用域被限制为声明它的文件
  3. 类数据成员: 仅存在一个该类别的副本,被所有对象共享
extern 用于不同文件之间共享相同的全局变量或函数(extern void func())
thread_local 仅可在其当前线程访问
  • 杂项运算符
   
sizeof 返回变量占空间大小
()?X:Y  
. / -> 访问类或者结构体内部成员
强制类型转换  
&/* 指针相关
  • 数学运算
  • #include<cmath>内置的一些数学运算
    • sin/con/tan
    • log (means ln)
    • pow(x,y) x^y
    • sqrt()
    • abs
    • floor
  • 字符串
    • C Style (Char String Style)
      • char a[] = "Hello"
      • char a[] = {'a','b','c'}
      • 在内存中,数组在初始化时,会自动将一个\0放在字符串的末尾
      • 方法
        • strcat
        • strcpy
        • strlen
        • strcmp
    • C++ include <string>
      • 定义 string str=" "
      • 复制 =
      • cat +
      • s.size
  • 输入输出
    • 基本输入输出流 (cin/cout)
      • cin >> x

指针

  • 定义 int *fp; float *fp
  • 指针数组
    • 用法1: char * s[10]来表示字符串
  • 引用,其实和指针有着微妙的区别 int i; int&r = i; // r是i的引用
    • 与指针的区别
      • 必须被链接到一块内存
      • 被初始化为一个对象之后不能指向另外一个对象
      • 智能在创建时初始化
    • 经常作为函数的参数或者是返回值
      • 参数 void swap(int &X,int &y)
        • 相当于传了一个形参,与实际参数的区别在于实际参数传进来的是值,而这里传入的其实是实际变量的指针(引用)
  • Example At ./code/hello.cpp

结构体

  •   typedef struct Person // 用了typedef之后所有的struct Person都可以简化为Person
      // 当然typedef也可以用来对其他类型别名    typedef unsigned short *uint16_t; uint16_t a;
      {
          char name[100];
          int age;
      } person1,person2,person3;
    
  • 用”.”访问属性
  • 可以直接作为函数参数输入 void print(struct Person person1)
  • 也可以有指向结构体的指针 strcut Person *person_pointer; person_pointer = person1;
    • 使用结构体指针访问对象的时候,必须要 person_pointer -> age

OOP

Class

  • Template
    • class Person{
          public:
            char* name;
            int age;
            // bool gender;  // Not Political Right   
            int gender;
              
      
      };
      
      Person Kujo_Jotaro;
      
    • 对于非private的属性,正常访问方式是通过”.”
  • 定义成员函数,在类定义内部(某域内,比如Public) void doSomething(..)即可
    • 或者是在类的外部用范围解析运算符:: double Person::getDouble(..)
      • 当然在这种scenario下类的里面也是要定义下这个函数的名字的
      • 类内的函数调用类内的变量,直接用类定义里面的变量名
        • 上面Example中的age,而不是Person.age或者是person0.age
    • 两者调用都用”.”,只有指针时候才用”->”
  • 类访问修饰符 public, protected, private
    • 对public变量,可以直接在类的外部进行赋值 person0.age = 18
      • 而private的变量,外部是不可以访问的,需要定义一个 void setAge()(在Public Field)
    • protected与private比的区别是可以在其派生类当中访问
      • private类型的可以被类内部或者友元访问
    • 继承对雷访问修饰符的变化 优先度 private > protected > public
      • old level & inherit level = new level
    • 默认类型是private 类默认是private继承,而struct默认是public继承
  • 构造与析构
    • 每次创建类的对象时候自动被调用,定义的时候不需要Return Type
      • People::People(char *_name, int _age): name(_name), age(_age){} 也可以用这种方式进行初始化
    • 析构 ~Person
      • 不带参数,没有返回值,作用是*在跳出程序之前释放内存
  • 拷贝构造函数 Person(const Person &obj){}
    • 如果没有,系统会自己建立一个
    • Application:
      • 利用已有的对象初始化对象 Person person1 = person0; (此时是显示调用了拷贝构造函数)
      • 当对象传入(as param)或者传出函数(return),隐式调用拷贝构造函数
    • 初始化方式
      • 拷贝初始化(调用拷贝构造函数) Person p1 = p2;
      • 直接初始化 p1 = Person(..);
  • 友元函数 定义在类的外部,但是可以访问protected&Private的函数 friend void setXXX()
    • 可以是函数也可以是类 fiend Person StandUser
    • 并不是成员函数,但是需要在类中定义
    • TODO: 为什么要产生这个东西?和用法
      • 就只是为了方便吗?那把这个方法放在public里面不是也可以起到效果?好像区别就在于友元函数不是类内部,是一种从类的外部模拟类的内部的形式)
  • This指针:
    • 每一个对象本质上都是通过this指针来访问自己的地址(是所有成员函数所隐含的 直接调用age相当于->age)
    • 友元没有this指针
    • this本质是一个隐式的常量指针 Person* get_address(){return this;}
  • 类的指针
    • 和一般的指针一样,在使用之前必须要进行初始化!
    • 其访问成员需要用”->”
    • Person* person_pointer; person_pointer->getADDR();
  • 静态成员
    • 可以将成员定义为static,创建的多个对象,静态成员都只会有一个副本
    • 初始化
      • 由于静态成员被所有对象共享,如果没有初始化语句,将会在第一个成员被初始化时被赋0
      • 不能将初始化放在类的定义
      • 但是可以在外部利用::运算符通过重新声明对其初始化 int Person::object_cnt = 0;
    • 静态成员函数不依赖于对象(via ::)
      • 只能访问静态数据,没有this指针

        继承 (Inherit)

  • class SubClass : public Class 派生类
    • 可以访问基类中的所有非private成员
    • 继承了所有的基类的方法(除了构造,拷贝构造,析构,重载以及友元)
    • 一般只使用public继承
    • 多继承 class Subclass : public class1, public class2
    • 继承的构造函数 (会隐式调用基类的构造函数,如果不specify会调用default constructor)
      • Constructors are not inherited. They are called implicitly or explicitly by the child constructor. However In C++11, Could
        • A Blog Clarifies That
      • 但是一般的类别都不太是default constructor(指没有输入参数,或者是输入参数全部确定的构造函数)
      • 方法一: SubClass::SubClass(char * new_element):OldClass(A,B,C){ _stand_name = standname; }
      • 方法二:在Derived Class内部定义一个对象
    • 虚继承: 构造函数需要调用最终虚基类的构造函数
      • 可以避免由于树状继承而产生多个对象的问题

重载 (Override)

  • 函数重载 / 运算符重载 : 在同一个作用域内对同一个变量名再次定义,但是参数列表与内部定义不一样
    • 函数重载定义方式就和一般的函数相同,就是注意要在类的定义里面定义多个同名的成员函数
    • 运算符重载 由关键字operator加上运算符的名字,也带返回值以及参数
  • 当调用重载函数的时候,编译器对使用的参数类型进行比较,选择合适的定义
  • 不能改变优先级和操作目数

多态 (polymorphism)

  • 当类之间存在层次的继承的时候,调用成员函数时,会因为函数对象的不同而执行不同的函数(可以认为是在不同对象之间的重载?虽然他们的参数也可能一样)
    • 当某一个指针指向A派生类的时候,调用的是A派生类的成员函数。
      • 指向不一样的对象,就会调用不一样的成员函数
    • 当要利用到多态的时候,基类中的方法必须定义为virtual不然定义的都会是基类的方法
  • 虚函数Virtual的本意是告诉编译器不要动态链接
    • 这样当编译器先接触到基类的某个方法的时候,不会直接与他建立连接,而是只建立了一个动态软连接,便于指向新的类别的对象
    • 纯虚函数 在类的定义的时候,如果你想要更好地在其派生类中实现功能,可在基类中定义一个纯虚函数:
      • virtual void something() = 0;

接口(抽象类)

  • 一个类中至少有一个函数被声明为虚函数,这个类就是一个抽象类
  • 设计抽象类的目的只是为了给其派生类做一个模板,因而抽象类不能被实例化为对象,只能作为接口
  • 如果一个派生类想要继承一个抽象类,必须要实现它的所有虚函数

Code Example

#include<iostream>
using namespace std;

class Person{
    public:
        char* name;
        static int object_cnt;
        int age;
        int gender;
        void getAge();
        void setName(char* _name);
        virtual void whoami();
        Person operator+(const Person& p);
        Person* getADDR();  // 返回一个指向当前对象的指针
        Person(char* _name,int _age, int _gender);       // Constructor DONT Need a type
        ~Person();
};

Person Person::operator+(const Person& p){
    char tmp_name[] = " ";
    Person person2return(tmp_name,10,0);
    person2return.age = this->age + p.age;
    cout << "The Op + Overrided" << endl;
    return person2return;
}

Person* Person::getADDR(){
    cout << this << endl;
    return this;
}

Person::~Person(){
}

void Person::whoami(){
    cout << "I'm a person" << endl;
}

void Person::setName(char* _name){
    this->name = _name;
}

void Person::getAge(){
    cout << this->age << endl;        // 这里不用Person.age,也不是对象名 peron.age
}

Person::Person(char* _name, int _age, int _gender){
    name = _name;
    age = _age;
    gender = _gender;
}

class NormalPerson: public Person{
    public:
        NormalPerson();
        ~NormalPerson();
        void whoami();
};

NormalPerson::NormalPerson():Person(name,age,gender){
    cout << "Creating a Normal Person Object" << endl;
}

NormalPerson::~NormalPerson(){
}

void NormalPerson::whoami(){
    cout << "I'm Normal Person:" << this->name << endl;
}

// 继承自Person
class StandUser: public Person{
    public:
        // using Person::Person;  // This line is somehow useless
        char* stand_name;
        StandUser(char * _standname);
        ~StandUser();
        void getNames();
        void getNames(int i);  // define the override getNames
        void whoami();
};

StandUser::StandUser(char * _standname):Person(name,age,gender){
    stand_name = _standname;
}

StandUser::~StandUser(){
}

void StandUser::whoami(){
    cout << "I'm a Stand User:" << this->stand_name << endl;
}

void StandUser::getNames(){
    cout << "Stand User:" << this->name << endl;
    cout << "Stand:" << this->stand_name << endl; 
}

void StandUser::getNames(int i){
    cout << "Checkin' Overriding' The SatndUsers' getName" << i << endl;
}


char name0[] = "Josuke";
char standname0[] = "Crazy Diamond";
// Person person0("Josuke",19,0);  //如果构造函数的输入参数为char*,会给一个Warning"Josuke" is a string constant not char*,将输入参数类型改成string就没有问题
Person person0(name0,19,0);
Person person1(name0,20,0);
Person* person_pointer0;
Person* person_pointer1;
Person* person_pointer2;

NormalPerson normalperson0;     //注意即使是normalperon0即使构造函数的参数为空 不能写成 NormalPerson normalperon0()
StandUser standuser0(standname0);

int Person::object_cnt = 2;

int main(){

    // person0.age = 20;
    // cout << person0.age << endl;

    // person_pointer = &person0;
    // person_pointer -> getADDR();
    // person0.getAge();
    // person0.getADDR();
    // cout << person0.object_cnt << endl;
    Person person2(name0,0,0);
    // person2 = person1 + person0;     // Test Overrided


    person_pointer0 = &person2;
    // cout << person_pointer0 -> age << endl;
    // cout << person2.age << endl;

    // * Teting Polymorphism
    person_pointer1 = &standuser0;
    person_pointer1->whoami();
    person_pointer1 = &normalperson0;
    person_pointer1->name = (char*)("⚾");
    person_pointer1->whoami();


    // standuser0.setName(name0);      // Use The setName inherited from Person
    // standuser0.getNames(3);

    return 0;
}

Qs

  • A default constructor is a constructor that either has no parameters, or if it has parameters, all the parameters have default values.