美文网首页
命名空间、构造函数、析构函数、继承、初始化列表

命名空间、构造函数、析构函数、继承、初始化列表

作者: 叶子扬 | 来源:发表于2019-10-11 17:11 被阅读0次
    C++.png

    构造函数(Constructor)

    • 构造函数(也叫构造器),在对象创建的时候自动调用,一般用于完成对象的初始化工作
    • 特点
      • 函数名与类同名,无返回值(void都不能写),可以有参数,可以重载,可以有多个构造函数
      • 一旦自定义了构造函数,必须用其中一个自定义的构造函数来初始化对象
    • 注意
      • 通过malloc分配的对象不会调用构造函数
      • 因为malloc是C的函数,不会调用C++的东西
    • 一个广为流传的、很多教程\书籍都推崇的错误结论:
      - 默认情况下,编译器会为每一个类生成空的无参的构造函数
      • 正确理解:在某些特定的情况下,编译器才会为类生成空的无参的构造函数
      • (哪些特定的情况?以后再提)
    构造函数的调用
    struct Person{
        int m_age;
        
        Person(){
            cout << "Person()" << endl;
        }
        Person(int age){
            cout << "Person(int age)" << endl;
        }
    };
    
    // 全局区
    Person g_p1;    // 调用Person()
    Person g_p2();  // 函数声明,函数名字是g_p2
    Person g_p3(20);// 调用Person(int)
    
    int main() {
    
        // 栈空间
        Person p1;      // 调用Person()
        Person p2();    // 函数声明,函数名字是p2
        Person p3(20);  // 调用Person(int)
    
        // 堆空间
        Person *p4 = new Person;    // 调用Person()
        Person *p5 = new Person();  // 调用Person()
        Person *p6 = new Person(20);// 调用Person(int)
        return 0;
    }
    
    
    默认情况下,成员变量的初始化

    如果自定义了构造函数,除了全局区,其他内存空间的成员变量默认都不会被初始化,需要开发人员手动初始化

    struct Person{
        int m_age;
    };
    
    // 全局区
    Person g_p1;    // 成员标变量初始化为0
    
    int main() {
    
        // 栈空间
        Person p1; // 成员标变量不初始化
        
        // 堆空间
        Person *p2 = new Person;        // 成员标变量不初始化
        Person *p3 = new Person();      // 成员标变量初始化为0
        Person *p4 = new Person[3];     // 成员标变量不初始化
        Person *p5 = new Person[3]();   // 3个对象的成员标变量初始化为0
        Person *p6 = new Person[3]{};   // 3个对象的成员标变量初始化为0
        return 0;
    }
    
    
    成员变量的初始化

    对象初始化

    Person(){
            memset(this, 0, sizeof(Person));
        }
    

    析构函数(Destructor)

    • 析构函数(也叫析构器),在对象销毁的时候自动调用,一般用于完成对象的清理工作
    • 特点:
      • 函数名以~开头,与类同名,无返回值(void都不能写),无参,不可以重载,有且只有一个析构函数
    • 注意:
      • 通过malloc分配的对象free的时候不会调用构造函数
      • 构造函数、析构函数要声明为public,才能被外界正常使用

    对象的内存管理

    • 对象内部申请的堆空间,由对象内部回收
    • 多注意setter和析构的内存管理

    声明和实现分离

    就是类的域不同::

    命名空间

    • 命名空间可以用来避免命名冲突
    • 命名空间不影响内存布局
    namespace YY {
        int g_age;
        class Person{
        public:
            
            Person(){
                cout << "Person()" << endl;
            }
            ~Person(){
                cout << "~Person()" << endl;
            }
            void run(){
                cout << "run()" << endl;
            }
        };
    };
    int main() {
    
        YY::g_age = 20;
        cout << "YY::g_age = " << YY::g_age << endl;
        
        YY::Person person = YY::Person();
        person.run();
        return 0;
    }
    // log:
    YY::g_age = 20
    Person()
    run()
    ~Person()
    

    思考:下边的代码能通过编译吗

    namespace FX {
        int g_age;
    }
    namespace YY {
        int g_age;
    }
    int main() {
        using namespace YY;
        using namespace FX;
        g_age = 20; // 报错:Reference to 'g_age' is ambiguous
        return 0;
    }
    

    命名空间的嵌套

    有个默认的全局命名空间,我们创建的命名空间默认都嵌套在它里面

    namespace YY {
        namespace XX {
            int g_age;
        }
    };
    
    int g_age;
    
    int main() {
        ::g_age = 20;
        ::YY::XX::g_age = 30;
        
        return 0;
    }
    
    namespace YY {
        namespace XX {
            int g_age;
        }
    };
    
    // 以下的用法都是合法的
    int main() {
        {
            using namespace YY::XX;
            g_age = 20;
        }
        
        {
            using  YY::XX::g_age;
            g_age = 20;
        }
        
        {
            YY::XX::g_age = 20;
        }
        
        return 0;
    }
    
    

    命名空间的合并

    以下2种写法是等价的

    namespace YY {
        int g_age1;
    };
    namespace YY {
        int g_age2;
    }
    
    namespace YY {
        int g_age1;
        int g_age2;
    }
    

    其他编程语言的命名空间

    • Java : Package
    • Objective-C : 类前缀

    继承

    • 继承,可以让子类拥有父类的所有成员(变量\函数)
    • C++中没有像Java、Objective-C的基类
      • Java:java.lang.Object
      • Objective-C:NSObject

    对象的内存布局

    成员访问权限

    • 成员访问权限、继承方式有3种

      • public:公共的,任何地方都可以访问(struct默认)
      • protected:子类内部、当前类内部可以访问
      • prvate:私有的,只有当前类内部可以访问(class默认)
    • 子类内部访问父类成员的权限,是以下2项中权限最小的那个

      • 成员本身的访问权限
      • 上一级父类的继承方式
    • 开发中用的最多的继承方式是public,这样能保留父类原来的成员访问权限

    • 访问权限不影响对象的内存布局

    初始化列表

    • 特点
      • 一种便捷的初始化成员变量的方式
      • 只能用在构造函数中
      • 初始化顺序只跟成员变量的声明顺序有关
    • 图片中的2种写法是等价的
    struct Student {
        int m_age;
        int m_height;
        Student(int age, int height){
            this->m_age = age;
            this->m_height = height;
        }
    };
    
    struct Student {
        int m_age;
        int m_height;
        Student(int age, int height): m_age(age), m_height(height){}
    };
    

    思考:
    m_age、m_height的值是多少

    int myAge(){return 20;};
    int myHeight(){return 170;};
    
    struct Student {
        int m_age;
        int m_height;
        Student(int age, int height): m_age(myAge()), m_height(myHeight()){}
    };
    
    int main() {
        Student s(10,140);
        return 0;
    }
    // 20, 170
    
    struct Student {
        int m_age;
        int m_height;
        // 警告:Field 'm_height' is uninitialized when used here
        Student(int age, int height): m_age(m_height), m_height(height){}
    };
    
    int main() {
        Student s(10,140);
        return 0;
    }
    // 未知、140
    

    构造函数的互相调用

    struct Student {
        int m_age;
        int m_height;
        Student():Student(0, 0){};
        Student(int age, int height): m_age(age), m_height(height){}
        
        void display(){
            cout << m_age << ", "<< m_height << endl;
        }
    };
    
    int main() {
        Student s0;
        s0.display();
        
        Student s(10,140);
        s.display();
        return 0;
    }
    // log:
    0, 0
    10, 140
    

    注意:下面的写法是错误的,初始化的是一个临时对象

    struct Student {
        int m_age;
        int m_height;
        Student(){
            Student(0, 0);
        };
        Student(int age, int height): m_age(age), m_height(height){}
        
        void display(){
            cout << m_age << ", "<< m_height << endl;
        }
    };
    
    int main() {
        Student s0;
        s0.display();
        
        Student s(10,140);
        s.display();
        return 0;
    }
    // log:
    -272632680, 32766
    10, 140
    

    初始化列表与默认参数配合使用

    • 如果函数声明和实现是分离的
      • 初始化列表只能写在函数的实现中
      • 默认参数只能写在函数的声明中
    struct Student {
        int m_age;
        int m_height;
        Student(int age = 0, int height = 0): m_age(age), m_height(height){}
        
        void display(){
            cout << m_age << ", "<< m_height << endl;
        }
    };
    
    int main() {
        Student s1;
        Student s2(20);
        Student s3(20, 170);
        
        s1.display();
        s2.display();
        s3.display();
        return 0;
    }
    // log:
    0, 0
    20, 0
    20, 170
    

    父类的构造函数

    • 子类的构造函数默认会调用父类的无参构造函数
    • 如果子类的构造函数显式地调用了父类的有参构造函数,就不会再去默认调用父类的无参构造函数
    • 如果父类缺少无参构造函数,子类的构造函数必须显式调用父类的有参构造函数

    继承体系下的构造函数示例

    struct Person {
        int m_age;
        Person(): Person(0){};
        Person(int age = 0): m_age(age) {}
    };
    
    struct Student: public Person {
        int m_no;
        Student(): Student(0, 0){};
        Student(int age, int no): Person(age), m_no(no) {}
        
        void display(){
            cout << m_age << ", "<< m_no << endl;
        }
    };
    
    int main() {
        Student s1;
        s1.display();
        
        Student s3(2,40);
        s3.display();
        
        return 0;
    }
    // log:
    0, 0
    2, 40
    

    构造、析构顺序

    构造和析构顺序相反

    struct Person {
        Person(){
            cout << "Person()" << endl;
        }
        ~Person(){
            cout << "~Person()" << endl;
        }
    };
    
    struct Student: public Person {
        Student(){
            cout << "Student()" << endl;
        }
        ~Student(){
            cout << "~Student()" << endl;
        }
    };
    
    int main() {
        Student s1;
        
        return 0;
    }
    // log:
    Person()
    Student()
    ~Student()
    ~Person()
    

    父类指针、子类指针

    • 父类指针可以指向子类对象,是安全的,开发中经常用到(继承方式必须是public)
    • 子类指针指向父类对象是不安全的

    相关文章

      网友评论

          本文标题:命名空间、构造函数、析构函数、继承、初始化列表

          本文链接:https://www.haomeiwen.com/subject/dnlxmctx.html