Skip to content

Latest commit

 

History

History
441 lines (385 loc) · 11.3 KB

l_10.md

File metadata and controls

441 lines (385 loc) · 11.3 KB

Lesson10 类:定义&访问权限&构造与析构&深浅拷贝



1. 类定义

  1. 类中允许存在变量与函数;
  2. 类定义末尾要加;
  3. 类中变量称为成员变量(member_data),函数称为成员函数(member_function)或方法(method)
  4. 类名遵循大驼峰命名规则。

封装

class Name {
  public:         // 访问权限
   int m_a;       // 成员函数

   void func() {  // 方法
       std::cout << "hello world" << std::endl;
   }
};

实例化对象

使用.运算符直接调用类内成员

#define PI 3.14
class Circle {
 // 访问权限
  public:  // 公共权限
 // 属性
   int m_r;  // 半径
 // 行为
   double calculateZC() {
     return 2 * PI * m_r;
   }
};

int main() {
   // 通过圆类 创建具体的圆(对象)
   // 实例化 通过一个类 创建一个对象
   Circle cl;
   // 给圆对象的属性进行赋值
   cl.m_r = 10;
   std::cout << "circumference: " << cl.calculateZC() << std::endl;  
}

2. 访问权限

以下所讨论的成员均不包括静态成员,有关友元继承的内容后面会学到

  • public公共权限
    类内,子类,友元,类外都可以访问
    class A {
      public:
       int m_a;
    
       void func() {
          m_a = 0;
       }
     };
    
    int main() {
      A obj;
      obj.m_a = 1; // legal
    }
  • protected保护权限
    类内,子类,友元可以访问 类外不可以访问
    class A {
      protected:
       int m_a;
    
       void func() {
          m_a = 0;
       }
     };
    
    int main() {
      A obj;
      obj.m_a = 1; // illegal
    }
  • private私有权限
    类内,友元可以访问 子类,类外不可以访问
    class A {
      private:
       int m_a;
    
       void func() {
          m_a = 0;
       }
     };
    
    int main() {
      A obj;
      obj.m_a = 1; // illegal
    }

总结如下表:

访问权限 类内&友元 子类 类外
public yes yes yes
protected yes yes no
private yes no no
  • class&struct struct默认公共权限
    class默认私有权限
    class C1 {
      int m_A;  // 默认权限 private
    };
    
    struct C2 {
      int m_A;  // 默认权限 public
    };
    
    int main() {
      C1 c1;
      c1.m_A=100; // error
    
      C2 c2;
      c2.m_A = 100;  // 100
    }

3. 构造函数 (constructor) 和析构函数 (destructor)

只有在创建对象时可以手动调用构造函数,其余时候不可以调用构造和析构函数
如果已有用户声明 (user-declared) 的构造函数,编译器生成的构造函数代码会接安插在用户写的代码 (explicit user code)

默认构造函数 (default constructor)

  1. 默认构造函数只在它需要的时候被编译器产生出来
  2. 如果没有用户定义的默认构造函数或是编译器生成的默认构造函数,默认构造函数为隐式的无用默认构造函数 (implicit trivial default constructor) 语法:ClassName()
    在类对象被创建时自动执行且只执行一次;函数可以有参数,可以重载
class A {
  public:
   A();
};

析构函数 (destructor)

语法:~ClassName()
在类对象被销毁前自动执行且只执行一次;函数不可以有参数,不可以重载

class A {
  public:
   ~A();
};

有参/无参构造

class A {
  public:
   A() {
     std::cout << "A_constructor" << std::endl;
   }
   A(int a) {
     m_a = a;
     std::cout << "A_constructor" << std::endl;
   }
   int m_a;
};

拷贝构造函数 (copy construction)

  1. 拷贝构造函数只在它需要的时候被编译器产生出来
  2. 如果没有用户定义的拷贝构造函数或是编译器生成的拷贝构造函数,拷贝构造函数为隐式的无用默认拷贝函数 (implicit trivial copy constructor)
  3. 自己实现的拷贝构造函数的形参类型必须为引用,一般加上const
  4. 不加引用会导致函数变成无限递归函数
class A {
  public:
   A() {
     std::cout << "A_constructor" << std::endl;
   }
   A(const A& obj) {
     m_a = obj.m_a;
     std::cout << "A_constructor" << std::endl;
   }
   int m_a;
};

构造函数调用

//括号法
A obj1;       //普通构造
A obj2(10);   //有参构造
A obj3(obj2); //拷贝构造

//显式法 以匿名对象为桥梁
A obj1;           //普通构造
A obj2 = A(10);   //有参构造 A(10)为匿名对象
A obj3 = A(obj2); //拷贝构造
A(obj1); // error

//隐式法 发生隐式类型转换
A obj1 = 10;   //有参构造
A obj2 = obj1; //拷贝构造

当有参构造函数只有一个参数时,且传入参数符合参数类型声明,调用该函数发生隐式类型转换时,该构造被称为转换构造函数 (converting constructors)

注意

  1. 调用默认构造函数时不要加(),否则编译器会认为是函数声明
  2. 不要利用拷贝构造函数初始化匿名对象而无其他对象接管,否则编译器会认为A(obj3)<=>A obj3为对象声明(line10)

一些奇怪的构造形式

A obj1(A(1));
// obj2(1); // 此行仅用于创建被拷贝对象
A obj3(A(obj2));
// 在C++11中,为了防止括号产生的歧义,可以这样写
A obj3{A(obj2)};

等价于

// 这里的等号不会调用拷贝构造
A obj1 = A(1);
A obj3 = A(obj2);
  • 匿名对象是构造函数的一个桥梁
  • 当匿名对象出现在等号右边时会被左边的对象接管,会省去基于等号的拷贝构造
  • 当匿名对象出现在等号左边,就会很快被释放掉
  • 显式调用其实就是在类外调用了类的构造函数,没有拷贝构造过程
  • 如果等号右边的匿名对象是由一个非对象参数构造,调用了有参构造
  • 如果右边的匿名对象是由一个对象构造,调用了拷贝构造

显示构造函数 (explicit constructor)

class A {
  public:
   A(int x){}
   A(const char* pString){}
}
int main() {
  A obj1 = B(5);
  A obj2 = B("a");
  A obj3 = 5;     // => A obj3 = A(5);
  A obj4 = "a";   // => A obj4 = A("a");
  A obj5 = 'a';   // => A obj5 = A(97);
}

添加explicit关键字禁用A(int x)函数的隐式转换

class A {
  public:
   explicit A(int x){}
   A(const char* pString){}
}
int main() {
  A obj1 = B(5);
  A obj2 = B("a");
  A obj3 = 5;     // => A obj3 = A(5);
  A obj4 = "a";   // => A obj4 = A("a");
  A obj5 = 'a';   // => A obj5 = A("a");
}

成员变量初始化

赋值先后顺序也如下

  • 定义时赋值(C++11)
class A {
  public:
   int m_A = 1;
   int m_B = 2;
   int m_C = 3;
};
  • 初始化列表
class A {
  public:
   A(int a, int b, int c)
       : m_A(a), m_B(b), m_C(c) {}

   int m_A;
   int m_B;
   int m_C;
};
int main() {
  A obj(1, 2, 3);
}
  • 构造函数赋值
class A {
  public:
   A(int a, int b, int c) {
      m_A = a;
      m_B = b;
      m_C = c;
   }

   int m_A;
   int m_B;
   int m_C;
};

初始化顺序

class A {
 public:
  A(int a)
   : m_a(a) { m_a = 3; }
  int m_a = 1;
};
int main() {
  A obj(2);
  std::cout << obj.m_a << std::endl; // output: 3
}

4. 深拷贝与浅拷贝

浅拷贝:简单的复制拷贝操作

p1在进行有参初始化时,在堆区申请了一个空间,p1的height指针就指向这个空间;
p2在进行拷贝初始化时使用的是编译器提供的浅拷贝;
浅拷贝是对成员变量的简单赋值,所以p2的height指针=p1的height指针,即两个height指针指向堆区的同一址;
函数test结束后,p1和p2把同一个空间释放了两次,程序崩溃

class Person {
  public:
   Person() {
      std::cout << "default constructor" << std::endl;
   }

   Person(int age, int height) {
      std::cout << "pram constructor" << std::endl;
      m_Age = age;
      m_Height = new int(height);
   }

   Person(const Person& p) {
      std::cout << "copy_construction" << std::endl;
      m_Age = p.m_Age;
      // 浅拷贝
      m_Height = p.m_Height;
   }

   // 析构函数 将堆区开辟的数据进行释放
   ~Person() {
      delete m_Height;
      m_Height = nullptr;
      std::cout << "destructor" << std::endl;
   }

   int m_Age;
   int* m_Height;
};

void test() {
   Person p1(18, 160);

   std::cout << "p1 age:" << p1.m_Age << " height:" << *p1.m_Height << std::endl; 
   // 注意 .运算符优先级高于*

   Person p2(p1);

   std::cout << "p2 age:" << p2.m_Age << " height:" << *p2.m_Height << std::endl;
   // error
}

深拷贝:在堆区重新申请空间

class Person {
  public:
   Person() {
      std::cout << "default constructor" << std::endl;
   }

   Person(int age, int height) {
      std::cout << "pram constructor" << std::endl;
      m_Age = age;
      m_Height = new int(height);
   }

   // 重写拷贝构造函数 解决浅拷贝的问题
   Person(const Person& p) {
      std::cout << "copy_construction" << std::endl;
      m_Age = p.m_Age;
      // 深拷贝
      m_Height = new int(*p.m_Height);
   }

   // 析构函数 将堆区开辟的数据进行释放
   ~Person() {
      delete m_Height;
      m_Height = nullptr;
      std::cout << "destructor" << std::endl;
   }

   int m_Age;
   int* m_Height;
};

void test() {
   Person p1(18, 160);

   std::cout << "p1 age:" << p1.m_Age << " height:" << *p1.m_Height << std::endl;

   Person p2(p1);

   std::cout << "p2 age:" << p2.m_Age << " height:" << *p2.m_Height << std::endl;
}

edit Serein