C++之封装

文章目录

  • 封装的意义
  • 成员变量和成员函数
  • 类的成员变量
    • 普通成员变量
    • mutable 可变成员变量
    • static 静态成员变量
  • 类的成员函数
    • 按功能来分
      • 构造函数
      • 拷贝构造函数
      • 构造函数调用规则
      • 析构函数
      • 类对象作为类成员时构造和析构顺序
    • 按特性来分
      • inline成员函数
      • const成员函数
      • static成员函数
  • 类的this指针
  • 类的友元
    • 全局函数作友元
    • 类作友元
    • 成员函数作友元
  • 类的大小

封装的意义

  • 将属性和行为作为一个整体,表现生活中的事物
  • 类可以把属性和行为放在不同的权限下,加以控制

成员变量和成员函数

在C++中,成员变量和成员函数分开存储。计算类所占内存大小时,只有非静态成员变量才属于类的大小,其余都不计算在类的内部。
空类创建的对象占的字节数为1

类的成员变量

类对象的构造顺序:

  1. 分配内存,调用构造函数,隐式/显式地初始化各数据成员;
  2. 进入构造函数,在构造函数中执行赋值和计算。

普通成员变量

类的初始化方式:

  • 传统初始化方式
Person(int a, int b, int c)
{
	m_a = a;
	m_b = b;
	m_c = c;
}
  • 初始化列表
Person(int a, int b, int c):m_a(a), m_b(b), m_c(c)
{
}

注:推荐使用初始化列表的方式,直接对成员变量进行初始化;而传统的初始化方式调用了默认构造函数,然后在构造函数中完成了赋值操作,所以体现出了效率的差异。

以下三种情况必须使用初始化列表:

  1. 初始化的成员变量是对象的情况
#include 
using namespace std;
class Test
{
public:
	Test(int, int, int)
	{
		cout<<"Test"<<endl;
	}
private:
	int x;
	int y;
	int z;
};

class Mytest
{
public:
	Mytest():test(1, 2, 3){
		cout << "Mytest" << endl;
	}
private:
	Test test;
};
  1. 初始化const修饰的类成员变量 或引用类型的成员变量
class Test
{
public:
	Test():a(10) { }
private:
	const int a;
};

或者

class Test
{
public:
	Test(int a):a(a) {}
private:
	int& a;
};
  1. 子类初始化父类私有成员变量,只能通过初始化列表
class Test
{
public:
	Test(){}
	Test(int x):m_x(x) {}
	void show() {cout<<m_x<<endl;}
private:
	int m_x;
};

class Mytest:public Test
{
public:
	Mytest():Test(110) {}
};

初始化成员列表使用注意事项:
构造函数列表初始化不是按照列表的顺序,而是按照变量的声明顺序。
例如:

#include 
using namespace std;
class Test
{
public:
	Test(int i): m_j(i), m_i(m_j) { }
	int get_i() const{
		return m_i;
	}
	int get_j() const{
		return m_j;
	}
private:
	int m_i;
	int m_j;
};
int main()
{
	Test test(10);
	cout << test.get_i << endl << test.get_j << endl;
	return 0;
}

输出结果:随机数 和 10

mutable 可变成员变量

一般const成员函数不能修改成员变量,但是只要成员变量被声明为mutable,则在const成员函数中也可以修改它。
mutable不能修饰静态成员变量

static 静态成员变量

静态成员变量的作用:

  1. 不管定义多少个类对象,静态成员变量都分配在全局数据区的内存,节省了存储空间;
  2. 当某变量需要修改时,只需要修改一次,所有对象的该变量都被修改了;
  3. 有一些变量和类相关而不是和对象相关,这些变量用静态成员变量。

使用:

class Rectangle
{
private:
	static int count;
	int high, width;
public:
	Rectangle() {count++;}
	Rectangle(int high_value, int width_value = 5);
	~Rectangle() {count--;}
	
	static int get_count() {return count;}	
};
int Rectangle::count = 0;

注意:静态成员变量 需要类内声明,类外初始化 (其实是定义,分配内存)

static静态成员变量的特性:

  1. 生命周期
    静态成员变量从类被加载到类被卸载,一直存在,在编译阶段就已经分配了内存;
    普通成员变量从类对象被加载到类对象结束。
  2. 共享方式
    静态成员变量是全类共享;
    普通成员变量是每个对象单独占有。
  3. 存储位置
    静态成员变量存储在静态全局区;
    普通成员变量存储在堆区或栈区。
  4. 初始化位置
    静态成员变量类外初始化;
    普通成员变量类内初始化。

类的成员函数

按功能来分

构造函数

构造函数用来初始化类对象的数据成员,只要类的对象被创建,就会自动调用构造函数
构造函数的语法:
类名() { }

  1. 构造函数没有返回值,也不写void
  2. 构造函数名与类名相同
  3. 构造函数有参数列表和函数体(可以都为空)
  4. 一个类可以有多个构造函数,类似于函数重载,在参数类型和数量上要有不同
  5. 构造函数自动调用,无需手动调用
  6. 构造函数不能定义成const。

构造函数分类:

  • 无参(默认)构造
Person()
{
	cout<<"Person的无参构造"<<endl;
}
或者
Person(int a=10)
{
	cout<<"Person的无参构造"<<endl;
}
  • 有参构造
Person(int age)
{
	m_age = age;
}
  • 拷贝构造
Person(const Person &p)
{
	m_age = p.age;
}

编译器会自动生成默认构造函数的情况:

  • 当用户没有定义构造函数
  • 在默认构造函数后面加=default,编译器会强行生成一个默认构造函数
    Person()=default;

拷贝构造函数

通过拷贝构造函数实现类对象之间的拷贝过程
参数:

  1. X&
  2. const X&
  3. volatile X&
  4. const volatile X&

拷贝构造函数是一种特殊的构造函数,函数名称必须和类名称相同,第一个参数必须是本类型的一个引用变量。
注意:必须是引用类型,否则会构成递归调用。

默认拷贝构造函数
形式:
编译器一般会给我们自动生成默认拷贝构造函数,只是使用老对象的数据成员对新对象的数据成员进行赋值操作。一般代码如下 :

Rectangle(const Rectangle &r)
{
	m_height = r.height;
	m_width = r.width;
}

默认拷贝构造函数存在的问题:

  1. 静态成员的复制问题
    默认拷贝构造函数不会处理静态成员变量,成员变量复制的时候不会复制静态成员变量。
    比如一个类中静态成员变量来统计对象的个数,在用一个对象rec1复制出对象rec2;当输出此时的对象个数的时候,仍显示是1个。此外,在销毁对象的时候,由于类的析构函数会调用两次,此时的计数器将边为负数。
    解决方法:自己定义拷贝构造函数,在拷贝构造函数中对计数器进行操作。

  2. 指针的复制问题(浅拷贝和深拷贝)
    浅拷贝:指的是对对象进行拷贝的时候,只对对象中的数据成员进行简单的赋值。多数情况下浅拷贝已经能够很好地解决问题,但是当出现动态成员,浅拷贝就会出现问题。
    C++之封装_第1张图片
    解决方法 :
    一是将拷贝构造函数设置为私有,防止默认拷贝构造函数的调用,这样拷贝的时候就会报错
    二是在自定义的拷贝构造函数中执行深拷贝(在堆区重新申请空间,进行拷贝操作),有了自定义拷贝构造函数就不会有默认拷贝构造函数

构造函数调用规则

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

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则:

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

析构函数

作用:释放对象使用的资源并销毁非static成员,不能重载,只能有唯一一个。
当析构函数未定义时,编译器自动生成,对象被销毁时自动调用。

类对象作为类成员时构造和析构顺序

C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
例如
class A { }
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员

那么当创建B对象时,A与B的构造和析构顺序时谁先谁后?
A构造——B构造——B析构——A析构

当其他类对象作为本类成员,构造时先构造类对象,再构造自身;析构的顺序与构造相反

按特性来分

inline成员函数

为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。

定义在类中的成员函数默认都是内联的,如果在类定义时就在类内给出函数定义,那当然最好。如果在类中未给出成员函数定义,而又想内联该函数的话,那在类外要加上 inline,否则就认为不是内联的。

class A
{
	int Foo(int a, int b) {} //内联函数
};

最好采用分文件编写:

//头文件
class A
{
public:
	void Foo(int a, int b);
};

//定义文件
inline void A::Foo(int a, int b) { }

const成员函数

const成员函数本质上是隐式修改this指针的类型
this指针本质上是指针常量,指针的指向不能被修改。在类成员函数后面加const,修饰的是this指向,让指针指向的值不可以被修改。

常函数:

  • 在成员函数后面加const构成常函数
  • 常函数不能修改成员属性
  • 常函数可以修改mutable修饰的成员变量

常对象:

  • 声明对象前加const,称该对象为常对象
  • 常对象只能调用常函数

static成员函数

静态成员函数为整个类所有,只能访问静态成员变量和静态成员函数。

类的this指针

  • this指针是类的指针,指向对象的首地址
  • 成员函数默认会隐式包含this指针形参
  • 在成员函数中,对成员变量的调用都会默认转换为用this指针对成员变量的调用
  • this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this

类的友元

友元的目的:可以让一个函数或者类访问另一个类中的私有成员

全局函数作友元

class Building
{
	friend void GoodGay(Building *building);
public:
	Building()
	{
		this->m_sittingroom = "客厅";
		this->m_bedroom = "卧室";
	}
	string m_sittingroom;
	
private:
	string m_bedroom;
};

void GoodGay(Building *building)
{
	cout << building->m_sittingroom << endl;
	cout << building->m_bedroom << endl;
}

类作友元

class Building
{
	friend class GoodGay;
public:
	Building();
	string m_sittingroom;
private:
	string m_bedroom;
};

成员函数作友元

class Building;
class GoodGay
{
public:
	GoodGay();
	void visit();
	void visit2();
private:
	Building *building;
};

class Building
{
	friend void GoodGay::visit();
public:
	Building();
	string m_sittingroom;
private:
	string m_bedroom;
};

类的大小

  1. 类的非静态数据成员数据类型的大小之和;
  2. 由编译器额外加入的成员变量大小,用来支持语言的某些特性(比如存在虚函数,就会为该类生成一个虚函数表,并在该类的每个实例中添加一个指向虚函数表的指针,32位的4字节,64位的8字节)
  3. 为了优化存取效率,采用内存对齐;
  4. 类的大小与构造函数、析构函数和其他的成员函数无关;
  5. 空类的大小是1个字节。

你可能感兴趣的:(C++,c++)