CPP_封装

C++面向对象的三大特性:封装,继承,多态。封装:使用一整套方法去创建一个新的类型,这叫类的封装。

拷贝构造函数、赋值构造函数和析构函数是类最重要的函数,被称为The Big Three。

与其他成员函数相比,The big three特殊之处在于,如果代码中没有提供这些函数的实现,C++编译器自动合成缺省的函数实现。 编译器提供的构造函数和析构函数是空实现。

有指针成员变量的类一定要提供The Big Three的实现,即使没有指针变量,作为良好的编程习惯也应该提供The Big Three的实现。

// person.h
#ifndef _PERSON_H_
#define _PERSON_H_

class Person{
    public :
        Person();
        Person(int myage, char *myname);
        Person(const Person &a);
        Person& operator=(const Person& other);
        ~Person(void);

        void set_age(int myage);
        int get_age(void) const;
        void set_other_age(Person &a, int b); 
        void print() const;

    private:
        int age;
        char *name;
};

#endif
// person.cc
#include "person.h"

#include <iostream>
#include <stdio.h>
#include <string.h>

using namespace std;

Person::Person():age(10), name(NULL)
{
    //nothing   
    cout<<"no param construct!"<<endl;
}

Person::Person(int myage, char *myname)
{
    age = myage;
    if(myname != NULL){
        name = new char[strlen(myname) + 1];
        this->print();
        strcpy(name, myname);
    }
    cout<<"param construct!"<<endl;
}

Person::Person(const Person &a)
{
    cout <<"copy constructor!"<<endl;
    if(a.name != NULL){
        name = new char[strlen(a.name)+1];
        this->print();
        strcpy(name, a.name);
        age = a.age;
    } else {
        name = NULL;
        age = 10;
    }
}
Person& Person::operator=(const Person &a)
{
    cout <<"= constructor!"<<endl;
    if(&a == this){
        return *this;
    }

    if(name != NULL){
        delete [] name;
        name = NULL;
    }
    if(a.name != NULL){
        name = new char[strlen(a.name)+1];
        this->print();
        strcpy(name, a.name);
        age = a.age;
    } else {
        name = NULL;
        age = 10;
    }

    return *this;
}


Person::~Person()
{
    if(NULL != name){
        delete [] name;
        name = NULL;
    }
    cout<<"destructor!"<<endl;
}
void Person::set_age(int myage)
{
    age = myage;
}

int Person::get_age(void) const
{
    return age;
}

void Person::set_other_age(Person& a, int b)
{
    a.age = b;
    //a.set_age(b);
}

void Person::print() const
{
    if(name != NULL)
        cout <<"value:" << name;
    cout<< " name=" << static_cast<void*> (name) << endl;
}
// main.cc
#include "person.h"

#include <iostream>
using namespace std;

int main(int argc, char *argv[])
{
    Person a(11, "xiaohhhhh");  
    Person b(13, "mingttttt");
    Person m;

    m = b;
    a.print();
    b.print();
    m.print();

    Person n(13, "qing");
    b=n;
    a=n;
    m=n;
    a.print();
    b.print();
    m.print();

    return 0;
}

0. 编译器提供的类函数

默认情况下,C++编译器至少给一个类添加4个函数:

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝
  • 赋值运算符operator=,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不在提供默认无参构造,但是会提供拷贝构造。
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数。

1. 构造函数

类名(){}
  • 和类同名,无返回值,用来初始化类的对象。
  • 有参数或无参数,因此构造函数可重载。

编译器提供默认无参构造函数,若声明新的,则采用新的构造函数。一般应创建一个无参的构造函数,因为创建了有参构造函数后,默认的无参构造函数被覆盖。

构造函数在对象创建后马上调用。

构造函数调用方式:括号法、显示法和隐式转换法。

#include <iostream>
#include <unistd.h>

using namespace std;

class Person
{
    public:
    Person(){
        cout << "constructor without parameters" << endl;
    }

    Person(int a){
        age = a;
        cout << "constructor with parameters" <<endl; 
    }

    Person(const Person &p){
        age = p.age;
        cout << "constructor copy" << endl;
    }

    ~Person(){
        cout <<"destructor" << endl;
    }

    int age;
};

void test01()
{
    cout << "括号法" << endl;
    Person p1_1;       // 无参构造函数
    Person p2_1(10);   // 有参构造函数
    Person p3_1(p2_1); // 拷贝构造函数
    cout << "P2 age = " << p2_1.age << endl;
    cout << "P3 age = " << p3_1.age << endl;
    //调用默认构造函数的时候不要加小括号
    //Person p1();    // 编译器会认为这行代码是函数的声明,不会认为创建对象

    cout << "显示法:" << endl;
    Person p1_2; 
    Person p2_2 = Person(10);
    Person p3_2 = Person(p2_1);
    Person(10); // 匿名对象,特点:当前行执行结束后,系统会自动回收掉匿名对象
    // 不要利用拷贝构造函数,初始化匿名对象
    // Person(p3); // 错误,编译器认为"Person(p3);"等价于"Person p3;",即p3重复定义

    cout<< "隐式转换法:" << endl;
    Person p1_3;
    Person p2_3 = 10;  // 有参构造
    Person p3_3 = p2_3; // 拷贝构造

    cout << endl;
}

2.拷贝构造函数

Person(const Person &a);

拷贝构造函数调用时机:

  • 使用一个已知创建完毕的对象来初始化一个新对象
  • 值传递方式给函数参数传值
  • 以值方式返回局部对象

深拷贝和浅拷贝

默认拷贝构造函数实现浅拷贝:指针成员变量指向同一存储区域,而不是重新分配区域再赋值(深拷贝)。

如果类有指针成员变量或类数据成员管理资源(如打开一个文件),默认构造函数并不会自动分配指针所指向的空间(默认构造函数仅提供浅拷贝),需要添加拷贝构造函数实现深拷贝。

如果想禁止一个类的拷贝构造,需要将拷贝构造函数声明为private。

3. 析构函数

~类名(){}
  • 析构函数无参数,不可重载。
  • 类名前加~的函数,无参数,无返回值。
  • 先调用析构函数->后销毁对象。

默认的析构函数不会释放指针成员变量分配的内存区域,可能造成内存泄露。有指针成员变量时必须实现析构函数。

4. 赋值操作函数(Assignment Operator)

Person& operator=(const Person& other);

基本与拷贝构造等价,operator=是一个整体,后面的()是参数。如果想禁止一个类的赋值函数,需要将拷贝构造函数声明为private。

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝的问题。

Person b=a; //拷贝构造函数
Person b;
b = a; //赋值函数 operator=

如果想禁止一个类的赋值函数,需要将赋值函数声明为private。

5. 构造函数的初始化列表

构造函数(): 属性1(值1), 属性2(值2),...{}

Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c)
{}
Person():age(10), name(NULL)
{}

const变量必须用初始化列表进行初始化。

6. this指针

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用一块代码。那么问题是:这一块代码是如何区分哪个对象调用自己的呢?

每个成员方法(非静态成员函数)都包含一个隐藏参数this,this是一个对象的指针,指向调用函数的对象。this不需要定义,直接使用即可。

  • 当形参和成员变量同名时,可用this指针区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

判断自赋值:

if(this == &a){
return *this;
}

C++中空指针也是可以访问成员函数的,要注意区别this指针,以保障代码的健壮性。

void showAag(){
    if(this == NULL){
        return;  //如果是空指针就返回,提高代码健壮性
    }
    cout << "Age is " << m_Age << endl; // 相当于this->m_Age
}

this指针本质是一个指针常量,指针的指向是不可以修改的

// 下行代码错误
this = NULL; //this指针指向的对象不可以修改-- Person *const this

7. 静态成员

静态成员就是在成员变量和成员函数前加上关键字“static”。可分为静态成员变量和静态成员函数。

静态成员变量

  • 所有对象共享同一份数据
  • 在编译阶段分配内存,存储在rw数据段,类sizeof不包含static变量大小
  • 类内声明,类外初始化

静态成员函数

  • 所有对象共享同一个函数
  • 可对象调用,也可直接类调用(A::goo())
  • 静态成员函数只能访问静态成员变量,不可访问非static成员变量。
  • 静态成员函数也有访问权限,private静态成员函数类外不能访问
  • static成员方法参数中不包含隐藏的this指针(this是一个对象的指针),不能使指针。

8. const修饰成员

常函数

  • 成员函数后加const,称这个函数时常函数
  • 常函数不可以修改成员属性
  • 成员变量声明时加上关键字mutable,在常函数中依然可以修改

常对象

  • 声明对象前加上const,称该对象为常对象
  • 常对象只能调用常函数
  • 常对象也可以修改mutable修饰的成员变量(不能修改普通成员变量)

成员变量

const成员变量,表示一旦初始化以后不会再改变的数据,必须在类的构造函数初始化列表中初始化,不能在函数体内赋值。

// martain.h
#ifndef _MARTAIN_H_
#define _MARTAIN_H_

class Martain{
public:
    Martain(int id);
    ~Martain();

    void fight() const;
    void hide() const;
    static int getCount();

private:
    static int  martainCount;
    const int m_id;
};

#endif

// martain.cc
#include "martain.h"

int Martain::martainCount = 0;

Martain::Martain(int id) 
    :m_id(id)
{
//  m_id = id; // const常量只能在初始化列表初始化
    martainCount++;
}

Martain::~Martain(){
    martainCount--;
}

int Martain::getCount(){
//  return martainCount + m_id;
    return martainCount ; // 静态函数不能访问非静态成员
}

void Martain::fight() const{
    
}

void Martain::hide() const{

}
// main.cc
#include "martain.h"
#include <iostream>

using namespace std;

void func(){
    Martain c(3);
    int count = Martain::getCount();
    cout << "count= " << count << endl;
}

int main(int argc, char *argv[])
{
    int count = Martain::getCount();    
    cout << "count= " << count << endl;

    Martain a(1);
    count = Martain::getCount();    
    cout << "count= " << count << endl;

    Martain b(2);
    count = b.getCount();   
    cout << "count= " << count << endl;

    func();
    count = Martain::getCount();    
    cout << "count= " << count << endl;

    return 0;
}
$ g++ main.cc martain.cc -o test
$ ./test
count= 0
count= 1
count= 2
count= 3
count= 2

9. set_other_age()同一类的不同对象可以访问其他对象的私有数据。

posted @ 2018-09-09 12:14  yuxi_o  阅读(922)  评论(0编辑  收藏  举报