zoukankan      html  css  js  c++  java
  • 【C++ 系列笔记】02 C++ 面向对象基础

    C++ 面向对象基础

    面向对象基础

    class Type {
       private:
        /* data */
       public:
        Type(/* args */);
        ~Type();
       protected:
    };
    
    Type::Type(/* args */) {}
    
    Type::~Type() {}
    
    • 权限
      • private 仅内部访问
      • public 均可访问
      • protected 内部和子类访问

    构造函数和析构函数

    C++ 默认提供三个函数,无参构造、拷贝构造、析构

    我们实任意现一个构造函数,无参构造就不会再提供。

    构造函数必须写在 public 下才能被调用到

    ...
       public:
        Type(/* args */);
        ~Type();
    ...
    

    匿名对象

    Type(/* args */);
    // 没有引用则会被立即释放
    

    拷贝构造函数

    Type(const Type& obj);
    
    • 被调用的条件

      通常的方式

      classNmae obj2(obj1);
      

      匿名对象

      // 使用匿名对象
      Type obj = Type(/* args */);
      

      隐式转换(匿名函数的另一种形式)

      // 一种隐式转换
      Type obj2 = obj1;
      // 相当于调用了 Type obj2 = Type obj2(obj1);
      
      // 类似的
      Type obj2 = 1;
      // 相当于滴啊用了 Type obj2 = calssName obj2(1)
      
      // 说白了,1 与 Type 不是一种类型,编译器会去寻找匹配的构造函数,来使得 1 通过构造隐式转换为 Type 类型
      

      函数调用,对象的值传递

      void fun(Type param);
      int main(){
          Type obj;
      	fun(obj);
          // 传值时会实例化一个新对象
      }
      

      以值的方式返回对象

      void fun(Type param){
          // ...
      	return param;
          // 返回时会实例化一个新对象
      }
      int main(){
          Type obj;
      	fun(obj);
      }
      
    • 注意

      • 二义性

        Type obj1();
        int main(){
        	Type obj1();
            // 这里不会实例化该类,编译器会认为这是一个函数声明
        }
        
      • 不能用拷贝构造初始化匿名对象

        匿名对象作为左值时,无法通过拷贝构造函数初始化

        Type obj(/* args */);
        Type(obj);
        
        // 错误,编译器认为第二行等价于
        Type obj;
        

        当作为右值时可以,即上方的使用匿名对象调用拷贝构造

    深拷贝和浅拷贝

    浅拷贝仅拷贝栈区的数据,某些数据是指针,其指向堆区中的数据不变。

    深拷贝则会重新申请堆区空间。

    Class1(const Class1& obj){
        mClass2Obj = new Class2(obj.class2Obj);
    }
    

    初始化列表

    Type(name, age): mName(name), mAge(age){
        // ...
    }
    

    类对象作为成员(组合)

    构造顺序:先对成员进行构造,然后对本类对象进行构造。

    析构顺序:完全相反。

    • 构造有参对象成员的方法

      class Person(){
          Head head;
         public:
          Person(headParam): head(headParam){
              // ...
      	}
      };
      

    explicit 关键字

    用来设定构造函数的 显式 特性,防止隐式转换。

    explicit Type(){
    	// ...
    }
    

    此时就不能进行上述的这种操作了。

    Type obj = 1;
    

    C++ 的动态内存分配(动态创建对象)

    运算符:new delete

    Type* obj = new Type;
    delete obj;
    

    注意不要这样写:

    void* p = new Type;
    delete p;
    // 释放失败
    
    • new 开辟数组

      Type* objArr = new Type[10];
      

      注意,开辟数组会调用类的默认无参构造,所以想要创建对象数组,必须提供无参构造。

      不过在栈区的数组可以指定有参构造如:

    Type objArr[10] = {Type(/* args /), Type(/ args */), …}

    
    - delete 释放数组
    
    ```cpp
    delete [] objArr;
    

    常量对象调用方法

    • const Type& 类型的对象不允许调用未使用const修饰的方法。
    class Type{
         private:
         	int data;
         int getData() const{
     		// 不允许修改成员,但 const Data& 类型的实例允许调用该函数
             return data;
         }
         int setData(int value) {
             // 允许修改数据,但 const Data& 类型的实例禁止调用该函数
             return data = value;
         }
     };
     void failure(const Data& obj) {
         // 报错
     	obj.setData(1);
     }
     void success(const Data& obj) {
         // 通过
     	obj.getData(1);
     }
     int main(){
         Type obj;
         success(obj);
         failure(obj);
         return 0;
     }
    

    静态成员

    • 变量

      class Type(){
         public:
      	static int foo;
          // 类内声明
      };
      int Type::foo = 1;
      // 类外初始化
      

      静态成员变量在类内声明,在类外初始化,在编译阶段分配内存。

    • 方法

      class Type(){
         public:
      	static int foo();
      };
      int Type::foo(){
      	// 实现
      }
      

      静态成员方法不可以访问实例的成员,只能访问静态成员变量。

    • 静态成员实现单例模式

      • 重载构造为私有
      • 内部维护一个私有静态的单一实例,并通过公有静态方法提供出去。

      示例

      #include <iostream>
      using namespace std;
      class Foo {
         private:
          // 唯一实例的引用
          static Foo& foo;
      
          // 重载构造函数,使外部无法访问
          Foo() {
              cout << "init" << endl;
          }
          Foo(const Foo& foo);
      
         public:
          // 提供该单一实例
          static Foo& getInstance() {
              return foo;
          }
          // 其他方法
          void method() {
              cout << "method" << endl;
          }
      };
      // new 这个单一实例
      Foo& Foo::foo = *(new Foo());
      
      int main() {
          Foo& foo = Foo::getInstance();
          foo.method();
      
          system("pause");
          return EXIT_SUCCESS;
      }
      

    类和对象的内存结构

    内存结构

    • 空类的数据类型大小为 1 字节,这一字节用于为其实例分配内存,没有实际意义。当类中存在成员时,该字节会消失。

    • 类的大小不包含成员函数,成员函数在类外。

      • 问题:**函数在类外,对象中不包含方法指针,那么方法是如何被对象调用的?****

        方法类似全局函数,可以直接 call,其第一个参数是 this。

    • 类数据类型的大小仅包含成员变量。(还要考虑内存对齐)

      • 扩展: #pragma pack(1) 可以使得编译器以 1 字节对齐内存。

    this 指针

    obj.method()相当于method(obj),成员方法默认有一个隐藏的参数 Type* this

    ==静态成员函数没有 this 指针

    method(Type* const this);
    
    • 实现链式编程

      Type& method(Type* const this){
          // ...
          return *this;
      }
      
    • this 为空

      下面这段代码是合法的

      Type* p = NULL;
      p->method();
      

      一个空对象也可以调用他的方法,只不过传入的 this 也为 NULL。

      所以很多时候可以看到这样的成员函数定义。

      void method(){
          if(this == NULL) return;
          this->attr = ...;
      }
      
    • this 的常量修饰(成员函数的修饰)

      this 的类型默认为 Type* cosnt,不允许修改指向

      注意

      const int*int const*相同,均表示 常量数据的指针,即该指针引用的数据不允许更改。

      int* const则不同,它表示指针常量,即 指针本身是常量*,不允许修改指向。

      要使方法内不允许通过 this 修改成员,则应用 const Type* const来修饰 this

      应使用如下方法,声明成员函数为 常函数

      void method() const{
      	// ...
      }
      
      • mutable 关键字

        用 mutable 修饰的成员变量允许在常函数中被修改。

        class Type {
           private:
            mutable int data;
           public:
            void setData(int value) const{
        		this->data = value;
                // 这是合法的
            }
        };
        

    友元

    • 友元函数

      当一个函数声明为一个类的友元,那么它就会被允许访问该类实例的私有成员

      class Type {
          friend void friendFun(Type& obj);
          // 友元声明
         private:
          int data;
      };
      void friendFun(Type& obj){
          obj.data = // ...
          // 合法访问私有成员
      	
      }
      

      类成员函数也可作友元函数,不过需要注意加上作用域

      class TypeA {
          friend void TypeB::friendMethod(TypeA& obj);
          // 友元声明,注意加作用域
         private:
          int data;
      };
      
    • 友元类

      当一个累声明为另一个类的友元,那么它就会被允许访问这个类的私有成员

      注意,类的友元是 单向的,且**无传递性**

      class TypeA {
          friend class TypeB;
          // 友元声明
         private:
          int data;
      };
      class TypeB {
         public:
          void visitA(TypeA& obj){
      		obj.data = //...
              // 合法访问私有成员
          }
      };
      

    运算符重载

    • 全局函数重载

      int operator+(Type& a, Type& b) {
          return a.data + b.data;
      }
      
    • 成员函数重载

      int Type::operator+(Type& a) {
          return this->data + a.data;
      }
      
    • 重载任意类型

      int Type::operator+(Type& a, int b) {
          return this->data + b;
      }
      

      注意,基本数据类型的运算符不可重载。

    • << 左移运算符重载

      通常情况下,重载 << 是为了实现 cout << obj;

      这种情况下,不会将其作为成员函数重载,因为成员函数固定了第一个参数为 this 指针。

      调用时是这样的 obj << // sth.,与 iostream 不相符,所以我们会使用 全局友元函数 去重载。

      ostream Type::operator<<(ostream& cout, Type& a) {
          return cout << a.data;
      }
      
    • ++ 递增递减运算符重载(以成员函数为例)

      • 前置(效率稍微高一些)

        Type& operator++(){
            this->data++;
            return &this;
        }
        
      • 后置

        Type operator++(int){
            Type temp = *this;
            // 拷贝一份原状态的对象
            this->data++;
            return temp;
        

    }
    ```

    • * -> 指针、取址运算符重载

      Type* operator->(){
      	return // ...
      }
      
      Type& operator*(){
      	return // ...
      }
      
      • 智能指针

        自动释放内存

        class SmartPointer{
           private:
            int* pointer;
           public:
            // 构造时托管一个指向堆中数据的指针
            SmartPointer(int* pointer){
        		this->pointer = pointer;
            }
            // 智能指针对象应开辟在栈上,当对象析构时顺便释放在堆上的数据
            ~SmartPointer(){
        		if(this->pointer){
        			delete pointer;
                    this->pointer = nullptr;
                }
            }
            // 重载
            SmartPointer* operator->(){
        		return this->pointer;
            }
            SmartPointer& operator*(){
        		return *this->pointer;
            }
        };
        
    • *=====* 赋值运算符重载(复杂数据类型自然 地带有 一个浅拷贝的赋值运算符实现)

      以成员函数为例

      Type& operator=(Type& a){
      	this->obj = new Foo(a.obj);
          this->data = a.data;
          return a;
      }
      
    • [] 中括号运算符重载

      以 Arr 类的成员函数为例

      elementType& operator[](int index){
          return this->arrPointer[idnex];
      }
      
    • 关系运算符重载

      • == 相等

        bool operator==(Type& a){
        	reeturn this->data == a.data;
        }
        
      • != 不等

        bool operator!=(Type& a){
        	reeturn this->data != a.data;
        }
        
    • ( ) 函数调用运算符

      注意,该运算符只能作为成员函数重载

      void operator()(){
      	// ...
      }
      
    • 注意,不要重载 && 和 ||

      重载后会使得其短路特性消失,此时便无法保证表达式内部操作的逻辑性。

      因为函数调用总会也必会对所有参数进行求值,这个过程可能导致被运算内容本身的改变,从而出现无法预知的异常。

    • 总结

      • = [] () ->

        一般通过成员函数重载

      • << >>

        一般通过全局函数配合友元重载

  • 相关阅读:
    java学习55天2020/8/29
    java学习51天2020/8/25
    java学习55天2020/8/31
    java学习49天2020/8/23
    java学习52天2020/8/26
    java学习48天2020/8/22
    2020.12.05
    2020.12.04
    2020.12.07
    2020.12.03
  • 原文地址:https://www.cnblogs.com/gaolihai/p/13149747.html
Copyright © 2011-2022 走看看