系列文章目录
文章目录
前言
封装作为C++三大特性之一,对于C++的面向对象编程有着非常重要的意义,那么封装的作用是什么呢?让我来介绍一下吧
一、封装是什么
封装(Encapsulation)是面向对象编程(OOP)的四大基本特性之一(封装、继承、多态、抽象)。它可以将类的实现细节隐藏起来,暴露出一个简洁、清晰的接口。封装提高了代码的可读性、安全性和易维护性,有利于更高效地进行软件开发。
二、封装的意义
- 1.将属性和行为作为一个整体表现生活中的事物
- 2.将属性和行为加以权限控制
- 3.语法 class 类名{ 访问权限: 属性/行为 }
1.意义一
比如:我们可以设计一个学生类,属性有姓名学号等
//学生类
class Student
{
public:
//给姓名赋值
void setName(string name)
{
m_name = name;
}
//给学号赋值
void setID(int id)
{
m_id = id;
}
//显示学生信息
void showStudent()
{
cout << "姓名:" << m_name << " 学号:" << m_id << endl;
}
string m_name;//姓名
int m_id;//学号
};
int main()
{
//创建学生对象
Student s1;
s1.setName("张三");
s1.setID(1);
s1.showStudent();
Student s2;
s2.setName("李四");
s2.setID(2);
s2.showStudent();
return 0;
}
2.意义二
访问权限有三种
公共权限 | public | 类内可访问 | 类外可访问 |
保护权限 | protected | 类内可访问 | 类外不可以访问 |
私有权限 | private | 类内可访问 | 类外不可以访问 |
这其中分为类内可访问、类外可访问、类外不可以访问,那么这些访问权限都是什么意思呢?
class Person
{
public://公共权限
string Name;
protected://保护权限
int Age;
private://私有权限
int Password;
public:
void func()//
{
Name = "Tom";
Age = 18;
Password = 123456;
}
};
int main()
{
Person p1;
p1.Name = "Jerry";
//p1.Age = 18;//Age是保护权限,类外访问不到
//p1.Password = 123456;//Password是私有权限,类外访问不到
p1.func();//func是公共权限,类外可以访问到
system("pause");
return 0;
}
代码中的Age和Password在类外都访问不到,而func()和Name在类外都能访问到 印证了上表。
3.补充
在C++中struct和class仅有一项区别
- struct的默认权限是公共public
- class的默认权限是私有private
三.对象特性
1.构造函数和析构函数
1.1意义
对象的初始化和清理是两个非常重要的安全问题
一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用一个对象或变量,没有及时清理,也会造成一定的安全问题
C++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。
编译器提供的构造函数和析构函数是空实现
- 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数有编译器自动调用,无需手动调用
- 析构函数:主要作用在与对象销毁前系统自动调用有,执行一些清理工作
1.2构造函数用法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
1.3析构函数用法:~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前加上符合~
3.析构函数不可以有参数,因此不可以发送重载
4.程序在对象销毁前自动调用析构,无需手动调用,而且只会调用一次
1.4示例
class Person
{
public:
Person()//构造函数可以有参数,但是不能有返回值,不能被重载
{
cout << "构造函数Person()调用" << endl;
}
~Person()//析构函数没有参数,不能有返回值,不能被重载
{
cout << "析构函数~Person()调用" << endl;
}
};
int main()
{
Person p1;
return 0;
}
构造函数Person()调用
析构函数~Person()调用
代码中并未调用构造 和析构函数,但是却执行了函数内部的语句,说明构造函数和析构函数时编译器自动调用。当对象被创建时调用了构造函数,而在释放对象时调用了析构函数,如果在程序结束前加上System("pause");那么这个现象就会变得很明显。
2.构造函数的分类及调用
1.分类方式
按参数分为:有参构造和无参构造
按类型分为:普通构造和拷贝构造
2.调用方式
括号法
显示法
隐式转换法
class Person
{
public:
Person()//构造函数可以有参数,但是不能有返回值,不能被重载
{
cout << "构造函数Person()调用" << endl;
}
Person(int a)//有参构造函数
{
age = a;
cout << "有参构造函数Person(int a)调用" << endl;
}
Person(const Person &p)//拷贝构造函数
{
age = p.age;
cout << "拷贝构造函数Person(const Person &p)调用" << endl;
}
~Person()//析构函数没有参数,不能有返回值,不能被重载
{
cout << "析构函数~Person()调用" << endl;
}
int age;
};
int main()
{
//1.括号法
Person p1;//默认构造函数调用
Person p2(10);//有参构造函数调用
Person p3(p2);//拷贝构造函数调用
//注意事项:调用默认构造函数时,不要加括号,否则会被编译器认为是函数声明
//Person p1();这是函数声明
//Person p1;这是调用默认构造函数
//2.显示法
Person p4;//默认构造函数调用
Person p5 = Person(10);//有参构造函数调用
Person p6 = Person(p5);//拷贝构造函数调用
Person(10);//匿名对象 特点:当前行执行结束后,系统会立即回收掉匿名对象
//3.隐式转换法
Person p7 = 10;//相当于写了Person p7 = Person(10);
Person p8 = p7;//拷贝构造函数调用
//注意事项:不要利用拷贝构造函数初始化匿名对象 编译器会认为Person(p7) == Person p7
//Person(p7);//编译器认为Person(p7) == Person p7
return 0;
}
3.拷贝构造函数调用时机
- 使用一个已经创建完毕的对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
class Person
{
public:
Person()//构造函数可以有参数,但是不能有返回值,不能被重载
{
cout << "构造函数Person()调用" << endl;
}
Person(int a)//有参构造函数
{
age = a;
cout << "有参构造函数Person(int a)调用" << endl;
}
Person(const Person &p)//拷贝构造函数
{
age = p.age;
cout << "拷贝构造函数Person(const Person &p)调用" << endl;
}
~Person()//析构函数没有参数,不能有返回值,不能被重载
{
cout << "析构函数~Person()调用" << endl;
}
int age;
};
void doWork1(Person p)
{
}
Person doWork2()
{
Person p1;
return p1;
}
int main()
{
//使用一个已经创建完毕的对象来初始化一个新对象
Person P1(10);
Person P2(P1);
//值传递的方式给函数传参
doWork1(P1);
//值传递的方式给函数返回值
Person P3 = doWork2();
return 0;
}
4.构造函数调用规则
默认情况下,C++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行拷贝
构造函数调用规则如下:
- 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
- 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
5.深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数调用" << endl;
}
Person(int a, int height)
{
Height = new int(height);
age = a;
cout << "有参构造函数调用" << endl;
}
~Person()
{
if (Height != NULL)
{
delete Height;
Height = NULL;
}
cout << "析构函数调用" << endl;
}
int age;
int *Height;
};
int main()
{
Person p1(10, 180);
Person p2(p1);
return 0;
}
这里我在堆区创建了变量,并且在函数析构的时候释放了,乍一看这段代码没什么问题,但是运行会发现报错了,因为拷贝构造函数让p1和p2两个对象的指针变量Height都指向了堆区的同一块内存,导致释放内存时释放了两次,而这个时候就需要深拷贝操作了。
Person(const Person &p)
{
age = p.age;
Height = new int(*p.Height);
cout << "拷贝构造函数调用" << endl;
}
我们自己实现拷贝构造函数将两个Height指向两块不一样的地址,这样就不会重复释放了。
6.初始化列表
class Person
{
public:
//传统初始化
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)
{
}
int m_A;
int m_B;
int m_C;
};
总结
本文简单介绍了C++三大特性之一的封装与部分对象特性,有些不足之处还望指出。