类与对象的思想&基础应用
面向对象的特点:封装、继承、多态
面向对象编程的特点:
(1)易维护:可读性高,即使改变了需求,由于继承的存在,只需要对局部模块进行修改,维护起来非常方便,维护的成本也比较低。
(2)质量高:可以重用以前项目中已经被测试过的类,使系统满足业务需求从而具有更高的质量
(3)效率高:在软件开发时,根据设计的需求要对现实世界的事物进行抽象,从而产生了类
(4)易扩展:由于继承、封装、多态的特性,可设计出高内聚、低耦合的系统结构,使系统更加灵活、更容易扩展,而且成本也比较低。
一、类声明
语法:
class 类名
{
访问权限:
属性
行为
};
跟结构体是一样的,只不过关键字是class
结构体是类的一个特定情况
声明对象的两种方式
CPeople op;//第一种
CPeople* op1 = new CPeople;//第二种
1.1、封装类的意义
1.1.1、在设计类的时候,属性和行为写在一起,表现事物
学生类:有学生姓名,学号;可以通过调用类中方法获取姓名&学号
类中的属性和行为都叫做成员
类中有成员变量/成员属性(属性)、成员函数/成员方法(行为);
类可以创建对象;
class STU
{
public:
//属性 ===> 成员属性/成员变量
string Stuname;
int StuId;
//行为 ===> 成员函数/成员方法
void ShowStu()
{
cout << "Nmae : " << Stuname << "\tStuId : " << StuId << endl;
}
//通过行为给属性赋值
void GetName(string name)
{
Stuname = name;
}
void GetStuId(int id)
{
StuId = id;
}
};
1.1.2、成员权限
类在设计时,可以把属性和行为放在不同的权限下,加以控制
访问权限有三种:
- public 公共权限:对类成员来说,类内可访问,类外可访问:①对其他类可访问;②对自定义函数可访问;③对主函数可访问
- protected 保护权限:对类成员来说,类内可访问,类外不可访问;并且子类可访问
- private 私有权限: 对类成员来说,类内可访问,类外不可访问;子类不可访问
类内不写访问修饰符的话,默认是private
class Person
{
public:
string m_Name;
protected:
string m_Car;
private:
int m_Password;
public:
void func()
{
m_Name = "dfsdsd";
m_Car = "ssss";
m_Password = 1232456;
}
};
1.2、struct和class区别
c++中的类可以看成c语言中的结构体的升级版,,结构体是一种构造类型,可以包含若干个成员变量,成员变量的类型可以不同。
关于class(类)的几点说明:
(1)类的定义的最后有一个分号,它是类的一部分,表示类定义结束,不能省略。
(2)一个类可以创建多个对象,每个对象都是一个变量
(3)类是一种构造类型,大小的计算方法和struct一样,需要字节对齐
(4)类成员变量的访问方法:通过 .或者->来访问
(5)成员函数是类的一个成员,出现在类中,作用范围由类来决定,而普通函数是独立的,作用范围是全局或者某个命名空间
在C++中 struct和class的区别就在于 默认的访问权限不同
区别:
struct 默认权限为公共,结构体外部可以访问其内部成员
class 默认权限为私有,类的外部不能直接访问内部成员;可以手动声明为public权限;
class C1
{
int classID; //private
};
struct MyStruct
{
int structID; //public
};
void Class_Func_StructClassDiff()
{
C1 a;
a.classID = 12;//不可访问
MyStruct b;
b.structID = 23;
}
1.3、成员属性设置为私有
优点1:将所有成员属性设置为私有,可以自己控制读写权限
优点2:对于写权限,我们可以检测数据的有效性
class mPerson
{
private:
string mName; //可读可写
int mAge; //可读可写(写年龄时检测数据有效性范围0--150)
string Msex; //只写
public:
void SetName(string name) //写名字
{
mName = name;
}
string GetName() //读名字
{
return mName;
}
void SetAge(int age) //写年龄
{
//进行检测写入数据的有效性
if (age < 0 || age > 150)
{
mAge = 10;
cout << "设置年龄有误!!!默认年龄为:";
return;
}
mAge = age;
}
int GetAge() //读年龄
{
return mAge;
}
void SetSex(string sex) //写性别
{
Msex = sex;
}
};
void Class_Func_GetSet()
{
mPerson mp1;
mp1.SetName("abcd");
cout << "姓名:" << mp1.GetName() << endl;
mp1.SetAge(2345);
cout << "年龄:" << mp1.GetAge() << endl;
mp1.SetAge(23);
cout << "年龄:" << mp1.GetAge() << endl;
mp1.SetSex("male");
}
结果如下:
二、对象的初始化和清理
对象的初始化和清理也是两个非常重要的安全问题。一个对象或者变量没有初始状态,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始
化和清理工作。
编译器提供的构造函数和析构函数是空实现
2.1、构造函数&析构函数
构造函数语法: 类名(){}
1. 构造函数,没有返回值也不写void
2. 函数名称与类名相同
3. 构造函数可以有参数,因此可以发生重载
4. 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
析构函数语法: ~类名(){}
1. 析构函数,没有返回值也不写void
2. 函数名称与类名相同,在名称前加上符号 ~
3. 析构函数不可以有参数,因此不可以发生重载
4. 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
class MyPerson
{
public:
MyPerson() //构造函数
{
}
~MyPerson() //析构函数
{
}
public:
int My_age;
};
void Class_Func_Constructor()
{
MyPerson p1;
}
运行结果:
程序在调用对象时候会自动调用构造一次构造函数
在栈上的数据,Class_Func_Constructor()运行完会自动调用一次析构函数
2.2、构造函数分类
方法一:有参构造&无参构造
class MyPerson
{
public:
//有参构造函数
MyPerson()
{
cout << "无参构造函数 / 默认构造函数" << endl;
}
//无参构造函数
MyPerson(int a)
{
My_age = a;
cout << "有参构造函数" << endl;
}
public:
int My_age;
};
方法二:普通构造&拷贝构造
class MyPerson
{
public:
//普通构造函数
MyPerson()
{
cout << "无参构造函数 / 默认构造函数" << endl;
}
MyPerson(int a)
{
My_age = a;
cout << "有参构造函数" << endl;
}
//拷贝构造函数
MyPerson(const MyPerson &p)
{
My_age = p.My_age;
cout << "拷贝构造函数" << endl;
}
//析构函数
~MyPerson()
{
cout << "class MyPerson 's Destructor" << endl;
}
public:
int My_age;
};
2.3、构造函数调用方法
①括号法
void Class_Func_Constructor_Call01()
{
MyPerson p1; //默认构造函数调用
MyPerson p2(10);//调用有参构造函数
MyPerson p3(p2);//调用拷贝构造函数----将P2的所有属性拷贝到P3这个对象上
cout << "P2 的年龄为: " << p2.My_age << endl;
cout << "P3 的年龄为: " << p3.My_age << endl;
}
注意:调用默认构造函数时,不用加()。会被编译器认为是函数声明,并不会认为在创建对象 MyPerson p1();
运行结果:
②显示法
void Class_Func_Constructor_Call01()
{
MyPerson p4;
MyPerson p5 = MyPerson(10); //显示法调用-有参构造
MyPerson p6 = MyPerson(p5); //显示法调用-拷贝构造函数
/*
注意:不要利用拷贝构造函数,初始化匿名对象。
编译器会认为 MyPerson(p6) <===> MyPerson p6; 会变成声明对象这种形式
MyPerson(p6);
*/
MyPerson(23);//称为匿名对象,当前行执行结束后,系统沟立即回收匿名对象
}
注意:不要利用拷贝构造函数,初始化匿名对象。
编译器会认为 MyPerson(p6) <===> MyPerson p6; 会变成声明对象这种形式 MyPerson(p6);
③隐式调用
void Class_Func_Constructor_Call01()
{
MyPerson p7 = 10; //等价于 MyPerson p7 = MyPerson(10);
MyPerson p8 = p7; //隐式法调用拷贝构造
}
2.4、拷贝构造函数调用时机
C++中拷贝构造函数调用时机通常有三种情况
①使用一个已经创建完毕的对象来初始化一个新对象
void test01()
{
Person man(100); //p对象已经创建完毕
Person newman(man); //调用拷贝构造函数
Person newman2 = man; //拷贝构造
//Person newman3;
//newman3 = man; //不是调用拷贝构造函数,赋值操作
}
②值传递的方式给函数参数传值
//相当于Person p1 = p;
void doWork(Person p1) {}
void test02() {
Person p; //无参构造函数
doWork(p);
}
③以值方式返回局部对象
Person doWork2()
{
Person p1;
cout << (int *)&p1 << endl;
return p1;
}
void test03()
{
Person p = doWork2();
cout << (int *)&p << endl;
}
2.5、构造函数调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
2.5.1、如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
class MyPeople
{
public:
MyPeople()
{
cout << "MyPeople 类的 默认构造函数调用" << endl;
}
MyPeople(int id)
{
m_ID = id;
cout << "MyPeople 类的 有参构造函数调用" << endl;
}
~MyPeople()
{
cout << "MyPeople 类的 默认析构函数调用" << endl;
}
int m_ID;
};
void Class_Func_Constructor_Call03()
{
MyPeople p1(18);
MyPeople p2(p1);
cout << "p2 的id为:" << p2.m_ID << endl;
}
运行结果:P2的ID为18,但在class中没有看拷贝构造函数。因此,是编译器默认添加了一颗拷贝构造函数,将成员属性进行了值拷贝
自己实现拷贝构造函数后:
2.5.2、如果用户定义拷贝构造函数,c++不会再提供其他构造函数
class MyPeople
{
public:
MyPeople(int id)
{
m_ID = id;
cout << "MyPeople 类的 有参构造函数调用" << endl;
}
~MyPeople()
{
cout << "MyPeople 类的 默认析构函数调用" << endl;
}
int m_ID;
};
void Class_Func_Constructor_Call03()
{
MyPeople p1(18);
MyPeople p2(p1);
cout << "p2 的id为:" << p2.m_ID << endl;
}
运行结果:如果自己写了有参构造函数,编译器就不再提供默认的无参构造函数了,但仍然会提供 拷贝构造函数
2.5.3、如果只写了一个拷贝构造函数,则编译器不在提供其他的普通构造函数
```cpp
class MyPeople
{
public:
MyPeople(const MyPeople &p)
{
m_ID = p.m_ID;
cout << "MyPeople 类的 拷贝函数调用" << endl;
}
int m_ID;
};
void Class_Func_Constructor_Call03()
{
MyPeople p1;
}
运行结果:
2.6、深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作
深拷贝:在堆区重新申请空间,进行拷贝操作
2.6.1、浅拷贝
同一类型的对象之间可以赋值,使得两个对象的成员变量的值相同,两个对象仍然是独立的两个对象,这种情况被称为浅拷贝.
一般情况下,浅拷贝没有任何副作用,但是当类中有指针,并且指针指向动态分配的内存空间,析构函数做了动态内存释放的处理,会导致内存问题。
2.6.2、深拷贝
当类中有指针,并且此指针有动态分配空间,析构函数做了释放处理,往往需要 ** 自定义拷贝构造函数,自行给指针动态分配空间**
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person默认构造桉树的调用" << endl;
}
Person(int age,int height) { /*有参构造函数*/
m_age = age;
m_height = new int(height); //在堆区开辟内存
cout << "Person有参构造函数的调用" << endl;
}
~Person() {
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int m_age; //年龄
int* m_height; //体重
};
int main()
{
Person p1(18,160);
cout << "p1的年龄是" << p1.m_age << "体重是" << *p1.m_height << endl;
Person p2(p1);
cout << "p2的年龄是" << p2.m_age << "体重是" << *p2.m_height << endl;
system("pause");
return 0;
}
可以看出在test01()函数中先调用有参构造函数定义了对象p1,再调用拷贝构造函数将p1的数据复制给p2。代码看似没有错误,运行可以看到结果如下:
可以发现程序运行出现了一个异常,这是什么原因呢?
这是由于编译系统在我们没有自己定义拷贝构造函数时,会在拷贝对象时调用默认拷贝构造函数,进行的是浅拷贝!即对指针拷贝后会出现两个指针指向同一个内存空间。
由于栈区的规则是先进后出,当执行完拷贝构造函数的时候,就会执行p2的析构函数,导致释放堆区开辟的数据。因此当执行p1的析构函数时就会导致内存释放2次,程序崩溃。
所以,在对含有指针成员的对象进行拷贝时,必须自己定义拷贝构造函数,达到深拷贝的目的,才能必变内存重复释放。
增加拷贝构造函数后的图示:
#include <iostream>
using namespace std;
class Person {
public:
Person() {
cout << "Person默认构造桉树的调用" << endl;
}
Person(int age,int height) { /*有参构造函数*/
m_age = age;
m_height = new int(height); //在堆区开辟内存
cout << "Person有参构造函数的调用" << endl;
}
//拷贝构造函数
Person(const Person& p) {
cout << "Person拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
m_height = new int(*p.m_height);
}
~Person() {
if (m_height != NULL) {
delete m_height;
m_height = NULL;
}
cout << "Person析构函数的调用" << endl;
}
int m_age; //年龄
int* m_height; //体重
};
int main(void)
{
Person p1(18,160);
cout << "p1的年龄是" << p1.m_age << "体重是" << *p1.m_height << endl;
Person p2(p1);
cout << "p2的年龄是" << p2.m_age << "体重是" << *p2.m_height << endl;
system("pause");
return 0;
}
2.6.3、总结
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
2.7、 初始化列表
作用:C++提供了初始化列表语法,用来初始化属性
语法: 构造函数():属性1(值1),属性2(值2)… {}
2.7.1、传统方式赋值
class MyPeople03
{
public:
//传统赋初值
MyPeople03(int a, int b, int c)
{
m_A = a;
m_B = b;
m_C = c;
}
public:
int m_A;
int m_B;
int m_C;
};
void Class_Func_Constructor_InitList()
{
//传统方法赋初值
MyPeople03 p1(1,2,3);
cout << "m_A = " << p1.m_A << "\tm_B = " << p1.m_B << "\tm_C = " << p1.m_C << endl;
}
2.7.2、初始化列表赋值
class MyPeople03
{
public:
//初始化列表赋初值
MyPeople03(int a,int b,int c) :m_A(a), m_B(b), m_C(c)
{
}
public:
int m_A;
int m_B;
int m_C;
};
void Class_Func_Constructor_InitList()
{
//初始化列表赋初值
MyPeople03 p2(7,8,9);
cout << "m_A = " << p2.m_A << "\tm_B = " << p2.m_B << "\tm_C = " << p2.m_C << endl;
}
2.8、类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
class A {}
class B
{
A a;
}
B类中有对象A作为成员,A为对象成员
当创建B对象时,A与B的构造和析构的顺序是谁先谁后?
class Phone
{
public:
Phone(string pName)
{
cout << "Phone 构造函数调用" << endl;
m_PName = pName;
}
~Phone()
{
cout << "Phone 析构函数调用" << endl;
}
string m_PName;
};
class MyPeople04
{
public:
MyPeople04(string name,string pName):m_Name(name),m_Phone(pName)
{
cout << "MyPeople04 构造函数调用" << endl;
}
~MyPeople04()
{
cout << "MyPeople04 析构函数调用" << endl;
}
string m_Name;
Phone m_Phone; //对象也可以用初始化列表赋初值
};
void Class_Func_Constructor_Object()
{
MyPeople04 p1("张三", "APPLE");
cout << p1.m_Name << " take " << p1.m_Phone.m_PName << endl;
}
结论:当其它类对象作为本类成员时,
构造的时候先构造类对象,再构造本身,
析构的时候先析构本身,再析构类对象
2.9、静态成员
2.9.1、静态成员变量
(1)基础定义&用法
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
class MyPeople05
{
public:
static int m_A; //静态成员变量
};
//int MyPeople05::m_A = 100; //静态成员变量需要类内声明,类外初始化
void Class_Func_Constructor_StaticObject()
{
MyPeople05 p1;
cout << "p1 的 m_A 为:" << p1.m_A << endl;
}
结果:报错,“无法解析的外部符号”,这类错误一般都是在链接过程报出。
当我们把静态成员在类外初始化后,则正常运行,运行结果如下,说明:
(1)静态成员变量必须类外初始化
(2)所有对象的静态成员都是同一块内存,某个对象更改为XXX之后,其余对象访问时就是XXX,而不是初始化的值。
(2)访问方法
静态成员变量 不属于某一个对象,所有对象都共享有同一份数据。
因此,有两种访问方式
(1)通对象访问
MyPeople05 p3;
cout << "p3 的静态成员变量值 m_A 为:" << p3.m_A << endl;
(2)通过类名访问
cout << "class MyPeople05 的静态成员变量值 m_A 为:" << MyPeople05::m_A << endl;
(3)注意:静态成员变量也是有访问权限的,类外不能访问私有的静态成员变量
2.9.2、静态成员函数
所有对象共享同一个函数
静态成员函数只能访问静态成员变量
(1) 基础定义&用法
class MyPeople05
{
public:
static void func()
{
cout << "static void func()调用" << endl;
}
};
(2) 调用方法
(1)通对象访问
MyPeople05 p4;
p4.func();
(2)通过类名访问
MyPeople05::func();
(3) 静态成员函数只能访问静态成员变量
静态成员函数 只能访问 静态成员变量 :因为静态成员变量只存一份,改一个之后,任何一个对象访问的时候都是更改后的值。
静态成员函数 不能访问 非静态成员变量:因为编译器无法区分是哪个对象的m_C需要更改为654。
class MyPeople05
{
public:
int m_C;
static int m_A; //静态成员变量
static void func()
{
m_A = 987; //静态成员函数 只能访问 静态成员变量
//m_C = 654; //err,静态成员函数 不能访问 非静态成员变量
cout << "static void func()调用" << endl;
}
}
(5)静态成员函数也有访问权限
class MyPeople05
{
public:
static int m_A; //静态成员变量
static void func1()
{
m_A = 987;
cout << "static void func1()调用" << endl;
}
private:
static void func2()
{
m_A = 987;
cout << "static void func2()调用" << endl;
}
};