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

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)
  • 子类指针指向父类对象是不安全的
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,723评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,485评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,998评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,323评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,355评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,079评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,389评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,019评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,519评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,971评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,100评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,738评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,293评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,289评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,517评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,547评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,834评论 2 345

推荐阅读更多精彩内容