C++复习二:类的封装

  1. C语言中的结构体将属性和行为分开处理,在调用行为时无法进行匹配
  2. C++中的Class将属性和行为封装到了一起,属性与行为不匹配无法运行

控制权限: public 公共的 protected 受保护的 private 私有的
在C++中 class 和 struct 一样,但是 struct 默认权限是 public, class 是 private的


构造函数和析构函数

构造函数:在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用
析构函数:对象销毁前系统自动调用,执行清理工作

  1. 构造函数函数名与类名相同,无返回值,不需要写void,可以重载
  2. 析构函数函数名与类名相同,无返回值,无参数,前面必须加~,不能重载
  3. 用户没写构造析构函数,系统会自动调用一个为空的函数。
class Person{
public: 
	Person(){cout << "构造函数" << endl;}
	~Person(){cout << "析构函数" << endl;}
private:
	int age;
}

构造函数的分类和调用

有无参数
  • 无参数(默认构造)
  • 有参数
按类型
  • 普通构造函数
  • 拷贝构造函数
class Person{
public:
	Person();
	Person(int age){m_age = age;}
	Person(const Person& p){m_age = p.age;}
private:
	int m_age;
};

void test()
{
	//构造函数的调用方式
	Person p1;  //无参构造,默认构造函数,不允许加(),否则编译器会认为这是函数的声明
	Person p2(1);  //有参构造,
	Person p3(p2);  //拷贝构造函数
	
	//显示调用
	Person p4 = Person(40);//Person(40) 是匿名对象,这里给他了一个名字p4,如果只是Person(40);那么编译器在执行完这行代码以后就会释放这个对象。
	
	//不能用拷贝构造函数去初始化一个匿名对象
	Person(p5); //编译器会报重定义,因为他会认为你写的是Person p5;
	Person p5 = Person(p4);  //写在右边就可以
	
	//隐式类型转换
	Person p6 = 100; //相当于Person p6 = Person(100);
	Person p7 = p6; //相当于Person p7 = Person(p6);
}
拷贝构造函数调用的时机
  • 用一个对象去初始化另一个对象
  • 以值传递的方式传给函数参数
  • 以值方式返回局部变量
//自己调用
Person p1(p);
//以值传递的方式传给函数参数
void doWork(Person p) //实际上是Person p = Person(p2)

void test()
{
	Person p2;
	dowork(p2);
}
//以值方式返回局部变量
void doWork2()
{
	Preson p3;
	return p3;  //编译器会自动拷贝一个新的对象
}
构造函数的调用规则
  • 当我们提供了有参构造函数,那么系统不会给我们提供默认构造函数,但会提供默认拷贝构造函数
  • 当我们提供了拷贝构造函数,那么系统不会给我们提供默认构造函数。

深拷贝和浅拷贝

系统 提供的默认拷贝构造函数只会进行简单的值拷贝,当类中有指针,且指针指向动态分配的内存,默认拷贝只会简单的进行复制,两个对象会指向同一块内存,当一个对象析构之后,另一个对象的析构就会发生内存错误。

class Person{
public: 
 Person(char *name, int age)
 {
 	m_name = (char *)malloc(strlen(name) + 1);
 	strcpy(m_name, name);
 	m_age = age;
 }
 
 //默认拷贝构造函数,浅拷贝
 Person(const Person& p)
 {
 	cout << "默认拷贝构造函数" << endl;
 }
 //深拷贝
 Person(const Person& p)
 {
 	m_name  = (char *)malloc(strlen(p.m_name) + 1);
 	strcpy(m_name, p.m_name);
 	m_age = p.m_age;
 }
 //析构函数
 ~Person()
 {
 	if(m_name != NULL)
 	{
 		free(m_name);
 		m_name = NULL;
 	}
 }
private:
	char *m_name;
	int m_age;
};

初始化列表

在构造函数后面加 :属性(值、参数),属性(值、参数),,,,

class Person
{
public:
	Person() : m_A(10), m_B(20), m_C(30){}
	Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c){}
private:
	int m_A;
	int m_B;
	int m_C;
};
类对象作为成员

类中定义的数据成员一般都是基本的数据类型,但是类中的成员也可以是对象,叫做对象成员

当调用构造函数时,首先按各对象成员在类定义中的顺序(和参数列表的顺序无关) 依次调用它们的构造函数,对这些对象初始化,最后再调用本身的构造函数。也就是说, 先调用对象成员的构造函数,再调用本身的构造函数。

#include <iostream>
#include <string>
using namespace std;

class Phone{
public:
	Phone(){cout << "手机的默认构造函数" << endl;}
	Phone(string name)
	{
		cout << "手机的有参构造函数" << endl;
		mPhoneName = name;
	}
	~Phone(){cout << "手机的默认析构函数" << endl;}
	
	string mPhoneName;
};

class Game{
public:
	Game(){cout << "游戏的默认构造函数" << endl;}
	Game(string name)
	{
		cout << "游戏的有参构造函数" << endl;
		mGameName = name;
	}
	~Game(){cout << "游戏的默认析构函数" << endl;}
	
	string mGameName;
};

class Person{
public:
	Person(){cout << "人的默认构造函数" << endl;}
	Person(string name, string phoneName, string gameName) : mName(name), mPhone(phoneName), mGame(gameName)
	{
		cout << "人的有参构造函数" << endl;
		//mName = name;
	}
	~Person(){cout << "人的默认析构函数" << endl;}
	
	string mName;
	Phone mPhone;
	Game mGame;
};

int main()
{
    Person p("张三","XIAOMI","PUBG");
    return 0;
}

/*输出结果为
手机的有参构造函数
游戏的有参构造函数
人的有参构造函数
人的默认析构函数
游戏的默认析构函数
手机的默认析构函数*/

explicit

explicit 关键字的作用是禁止通过构造函数进行隐式转换

  • explicit 只能用于修饰构造函数,防止隐式转换
  • 只能针对单参数的构造函数(或者出了第一个参数外其余参数都有默认值的多参数构造函数)
class MyString
{
public:
	explicit MyString(int n)
	{
		cout << "MyString(int n)!" << endl;
	}
	MyString(const char* str)
	{
		cout << "MyString(const char* str)" << endl;
	}
};

int main()
{
	MyString str1 = 1;//错误
    //隐式转换,MyString str1 = MyString(1)或者MyString str1 = MyString(“1”)
	//加上了explicit之后,禁止了这种隐式转换
	MyString str2(10);//正确
	//寓意非常明确,给字符串赋值
	MyString str3 = "abcd";
	MyString str4("abcd");
	return EXIT_SUCCESS;
}

静态成员变量和静态成员函数

在一个类中,若将一个成员变量声明为static,这种成员变量成员静态成员变量。与一般的成员变量不同,无论建立多少个对象,都只有这一个静态变量,也就是说,多个对象共享一个数据

class Person{
public:
	person();
	//静态成员变量
	static int m_age;//静态成员变量,在类内声明,在类外初始化
	//静态成员函数,不可以访问普通成员变量,可以访问静态的成员变量
	static void func()
	{
		cout << "Person::func()" << endl;
	}
	//普通成员函数,可以访问普通成员变量,也可以访问静态的成员变量
	void myFunc();
private:
	static int m_other;
}
//类外初始化
int Person::m_age = 0;
int Person::m_other = 0; //加上作用域之后编译器会认为你还是在类内初始化的

void test()
{
//通过对象访问数据
	Person p1;
	p1.m_age = 10;
	
	Person p2;
	p2.m_age = 20;
	
	cout << "p1 = " << p1.m_age << endl; //20
	cout << "p2 = " << p2.m_age << endl; //20 共享数据
	
	//通过类名访问数据
	cout << "通过类名访问数据" << Person::m_age << endl;
	
	//静态成员函数的调用
	p1.func();
	p2.func();
	Person:func();
}

C++对象模型存储

class Person
{
public:
	int a; //非静态成员变量,属于对象
	void func(); //非静态成员函数,不属于对象
	static int b; //静态成员变量,不属于对象
	static void func2();//静态成员函数,不属于对象
}

///sizof(Person) = 4;

/*
#pragma pack(1)
可以改变内存对齐的大小
*/
  • 成员变量和成员属性是分开存储的
  • 空类的大小为1,内部char 会维护这个地址
  • 只有非静态成员变量才属于对象本身

this指针

this 指针指向被调用的成员函数所属的对象。在调用函数时,会将this指针传过去,隐藏于每个类的非静态成员函数中,永远指向当前的指针
非静态成员函数才有this指针
class Person{
//当形参和成员变量同名时
	Person(int age)
	{
		//age = age;编译器不知道age是指哪个
		this->age = age;
	}
	int age
//链式编程,当盛园函数需要返回当前对象时,可以返回对象的引用,return *this;
}

空指针访问成员函数

  • 如果成员函数中没有用到this指针,那么空指针可以直接访问
  • 如果成员函数用到了this指针,那么就要注意,可以加一个if判断

常函数和常对象

常函数

class Person
{
public:
	//常函数,实际是修饰const Type * const this;
	void func() const
	{
		//a = 100;错误,常函数内不允许修改指针的指向
		b = 1000;
	}
	
	int a;
	mutable int b;//如果执意要修改,在变量类型前加 mutable
}
//常对象,不允许调用普通的成员函数,只可以调用常函数
const Person p;

友元

友元函数

访问类中的私有属性
全局函数作友元函数,将声明放到类中,并在前面加friend关键字;

class Person{
public:
	friend void func(Preson *p)//友元函数的声明
	int a;
private:
	int b;
}

void func(Preson *p)
{
	cout << p->a << endl; //没问题,a是public
	cout << p->b << endl; //有问题,b是private,可以将函数定义为友元函数
}

友元类

让一个类可以访问另一个类的私有数据

class Person{
	friend class other; //友元类
}

other里面的成员函数访问Person的私有属性
- 友元类是不可继承的
- 友元类是单向的
- 友元类是不可传递的

成员函数作友元函数

class Person{
   //让Other的成员函数作Person的友元
	friend void Other::func();
}

运算符重载

- 如果想让自定义类型进行运算,系统无法识别,就需要对运算符进行重载
- 方法:在成员函数 或 全局函数 中重写一个函数,函数名为 operator+(){}
- 运算符重载可以提供多个版本

+号运算符重载

class Person
{
public:
    Person(){};
    Person(int a, int b) : m_A(a), m_B(b){}
    //利用成员函数,完成运算符重载
    Person operator+(Person &p)
    {
        Person tmp;
        tmp.m_A = this->m_A + p.m_A;
        tmp.m_B = this->m_B + p.m_B;
        return tmp;
    }
    int m_A;
    int m_B;
};

利用全局函数实现运算符重载
Person operator+(Person &p1, Person &p2)
{
    Person tmp;
    tmp.m_A = p1.m_A + p2.m_A;
    tmp.m_B = p1.m_B + p2.m_B;

    return tmp;
}

Person operator+(Person &p, int a)
{
    Person tmp;
    tmp.m_A = p.m_A + a;
    tmp.m_B = p.m_B + a;

    return tmp;
}

void test()
{
    Person p1(10,20);
    Person p2(20,20);

    Person p3;
    p3 = p2 + p1;

    cout << p3.m_A << p3.m_B << endl;

    p3 = p2 + 20;
    cout << p3.m_A << p3.m_B << endl;

}

左移运算符重载

class Person
{
    friend ostream& operator<<(ostream &cout, Person &p);
public:
    Person(){};
    Person(int a, int b) : m_A(a), m_B(b){}

private:
    int m_A;
    int m_B;
};

//返回值为cout,因为之后 << endl 不知道应该流向哪里
//左移函数不能使用成员函数实现,如果使用成员函数,那么就需要调用对象,不符合原本的书写
//因为要访问私有数据,故使用友元函数
ostream& operator<<(ostream &cout, Person &p)
{
    cout << "m_A = " << p.m_A << "; m_B = " << p.m_B;

    return cout;
}

void test()
{
    Person p(10,20);

    cout << p << endl;
}

赋值运算符重载

class Person
{
public:
    Person(int a)
    {
        m_A = a;
    }
    int m_A;
};

void test01()
{
    Person p1(10);
    Person p2(20);
    p2 = p1; //并没有进行运算符重载,但是还是能够进行肤质,是因为一个类创建时,会给出一个 operator=的函数,只会进行简单的赋值操作
    cout << p1.m_A <<  endl;
    cout << p2.m_A <<  endl;
}

class Person2
{
public:
    Person2(char *name)
    {
        m_name = new char[strlen(name) + 1];
        strcpy(m_name, name);
    }
	//进行运算符= 的重载,防止出现深拷贝浅拷贝的问题
	//在创建之前,需要先判断之前是否有申请的空间,如果有,就释放
	//返回本身
    Person2& operator=(const Person2 &p)
    {
        if(this->m_name != NULL)
        {
            delete[] m_name;
            m_name = NULL;
        }

        m_name = new char[strlen(p.m_name) + 1];
        strcpy(m_name, p.m_name);

        return *this;
    }

    ~Person2()
    {
        if(this->m_name != NULL)
        {
            delete[] m_name;
            m_name = NULL;
        }
    }

    char *m_name;
};

void test02()
{
    Person2 p1("狗剩");
    Person2 p2("狗蛋");

    Person2 p3("");

    p3 = p2 = p1;
    cout << p3.m_name <<  endl;
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值