C++面向对象的三大特征:封装、继承、多态
C++认为万事万物结为对象,对象上有其属性和行为
一、封装
意义:①将属性和行为作为一个整体,表现生活中的事物
②将属性和行为加以权限控制
语法:class 类名 { 访问权限: 属性 / 行为 }
权限:公共权限 public 类内可以访问,类外可以访问
保护权限 protected 类内可以访问,类外不可以访问(子可以访问父)
私有权限 private 类内可以访问,类外不可以访问(子不可以访问父)
struct 和 class 区别:struct默认权限是public ;class默认权限是private
成员属性设置为私有: ① 可以自己控制读写权限
② 对于写可以检测数据的有效性
Class Student()
{
public: //公共权限 类内可以访问,类外可以访问
//行为
void showStudent()
{
cout << "姓名:" << m_strName << "学号:" << m_nId <<endl;
}
protected: //保护权限 类内可以访问,类外不可以访问(子可以访问父)
void setName(const string &strName)
{
m_strName = strName;
}
private: //私有权限 类内可以访问,类外不可以访问(子不可以访问父)
//属性
string m_strName;
int m_nId;
};
对象的初始化和清理是非常重要的安全问题:
一个对象或者变量没有初始状态,对其使用后果是未知的
同样的使用完一个对象或变量,没有及时清理,也会造成一定安全问题
构造函数和析构函数可以解决以上问题,如果我们不提供构造和析构函数,编译器会提供空实现的对应函数
1.1 构造函数:
主要作用在创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。
语法:类名(){}
① 构造函数,没有返回值也不写void
② 函数名称与类名相同
③ 构造函数可以有参数,可以重载
④ 程序在调用对象时自动调用构造,无须手动调用
构造函数分类:
按照参数分:有参构造 和 无参构造
按照类型分:普通构造 和 拷贝构造
构造函数调用: 括号法 显示法 隐式转换法
拷贝构造函数的调用:
① 使用一个已经创建完毕的对象来初始化一个新对象
② 值传递的方式给函数参数传值
③ 值方式返回局部对象
深拷贝:简单赋值拷贝操作
浅拷贝:在堆区重新申请空间,进行拷贝操作 (系统默认 浅拷贝)
注意:如果属性有堆开辟的,一定要自己提供拷贝构造函数,防止浅拷贝问题
构造函数调用规则:
默认情况下,C++编译器至少会给一个类添加3个函数
① 默认构造函数(无参,函数体为空)
② 默认析构函数(无参,函数体为空)
③ 默认拷贝构造函数,对属性进行值拷贝
如果用户定义有参构造函数,C++不再提供默认无参构造函数,但会一个默认拷贝构造
如果用户定义拷贝构造函数,C++不再提供其他构造函数
1.2 析构函数:
主要作用在对象销毁前系统自动调用,执行一些清理工作。
语法:~类名(){}
① 析构函数,没有返回值也不写void
② 函数名称与类名相同,在名称前加符号 ~
③ 构造函数不可以有参数,不可以重载
④ 程序在调用对象时自动调用析构,无须手动调用
class Person()
{
public:
Person() //默认构造
{
cout << "默认构造函数调用" << endl;
}
Person(int age,int height) //有参构造
{
m_nAge= age;
m_nHeight = new int(height);
cout << "有参构造函数调用" << endl;
}
//自己实现拷贝构造函数,解决浅拷贝带来的问题
Person(const Person &p) //拷贝构造
{
cout << "拷贝造函数调用" << endl;
m_nAge= p.m_nAge;
//m_nHeight = p.m_nHeight; //编译器默认实现的代码
//深拷贝操作 堆区开辟新内存空间
m_nHeight = new int(*p.m_nHeight);
}
~Person() //析构函数
{
if(!m_nHeight)
{
delete m_nHeight;
m_nHeight = NULL;
}
cout << "析构造函数调用" << endl;
}
private:
int m_nAge;
int*m_nHeight ;
}
/////////////////////////拷贝调用////////////////////
//1.使用一个已经创建完毕的对象来初始化一个新对象
void doWork0()
{
Person p1;
Person p2(p1);
}
//2.值传递的方式给函数参数传值
void doWork1(Person p)
{
}
//3.值方式返回局部对象
Person doWork2()
{
Person p1;
return p1;
}
/////////////////////////////////////////////////////////
int main()
{
//1.括号法
Person p1; //默认构造函数调用 不要加()
Person p2(10); //有参构造函数调用
Person p3(p2); //拷贝构造函数调用
//2.显示法
Person pA = Person(10); //默认构造
Person pA = Person(p2); //拷贝构造
Person(10); //匿名对象 特点:当前执行结束,系统自动回收
//不要利用拷贝构造函数 初始化匿名对象,编译器认为Person(pA) === Person pA; 报错
//Person(pA);
//3.隐式转换法
Person p4 = 10; //相对与 Person p4 = Person(10);
Person p4 = pA; //拷贝构造
system("puase");
return 0;
}
二、继承
语法:class 子类(派生类):继承方式 父类(基类)
多继承语法:class 子类(派生类):继承方式 父类1(基类),继承方式 父类2(基类)
注:多继承父类中同名成员,用作用域区分
继承方式:公共继承、保护继承、私有继承
注:父类私有成员属性也被继承了,但被编译器隐藏了,无法访问。
继承中构造和析构顺序:
父构造 =》子构造,子析构 =》父析构
继承同名成员 / 同名静态成员处理方式 :
访问子类同名成员,直接访问;访问父类同名成员,需要加作用域。
class Base
{
public:
Base(){m_a = 100}
void func();
void func(int num);
int m_a;
static int m_b;
};
int Base::m_b = 100;
class Son : public Base
{
public:
Base(){m_a = 200}
void func();
int m_a;
static int m_b;
};
int Son ::m_b = 200;
int main()
{
Son s;
cout << "son m_a:" << s.m_a << endl;
s.func();
//通过子类对象,访问父类同名成员,需要加作用域
cout << "Base m_a:" << s.Base::m_a << endl;
s.Base::func();
//注意:子类出现父类同名成员函数,子类同名成员函数会隐藏父类中所有同名成员函数
//访问需要加作用域
s.Base::func(100);
//类名访问静态成员 (访问函数同理)
Son::m_b;
Son::Base::m_b; //第一:: 代表类名方式访问 第二::代表访问父类作用域下
sysytem("pause");
return 0;
}
菱形继承:当菱形继承,两父类拥有相同数据,需要作用域区分。
数据只需一份,造成资源浪费;利用虚继承解决菱形继承问题。
虚继承语法:继承前 加关键字virtual 基类变成虚基类
class Animal
{
public:
int m_age;
}
class Horse : virtual public Animal
{
public:
int m_age;
};
class Donkey: virtual public Animal
{
public:
int m_age;
};
class Mule : public Horse ,public Donkey{};
int main()
{
Mule mule;
mule.Horse::m_age = 18;
mule.Donkey::m_age = 28;
//虚基类指针赋值,结果只有一个
cout << "Horse Age:" << mule.Horse::m_age << endl; //28
cout << "Donkey Age:" << mule.Donkey::m_age << endl; //28
cout << "Mule Age:" << mule.m_age << endl; //28
system("pause");
return 0;
}
三、多态
静态多态:函数重载和运算符重载属于静态多态,复用函数名
动态多态:派生类和虚函数实现运行时多态
区别:静态多态函数地址早绑定,编译阶段确定函数地址;动态多态函数地址晚绑定,运行时确定函数地址。
满足条件:① 有继承关系 ② 子类重写父类中的虚函数
使用条件:父类指针引用指向子类对象。
抽象类特点:无法实例化对象;子类必须重新抽象类中纯虚函数,否则子类也属于抽象类。
虚析构,纯虚析构:解决父类指针释放子类对象;需要具体函数实现。(如果子类中没有堆区数据,可不写为虚析构后纯虚析构)
//抽象类
class Animal
{
public:
Animal(){};
//纯虚析构 利用虚析构解决父类指针释放子类对象时不干净
//需要声明,也需要实现
virtual ~Animal() = 0;
//纯虚函数 虚函数指针,占四字节
virtual void speak() = 0;
};
//纯虚析构实现
Animal::~Animal()
{
cout << "Animal 纯虚析构" << endl;
}
class Cat :public Animal
{
public:
Cat(string name)
{
m_strName = new String(name);
}
~Cat()
{
if(m_strName != NULL)
{
delete m_strName;
m_strName = NULL;
}
}
//重写 函数返回值类型 函数名 参数列表完全相同
speak()
{
cout << "Cat Speak" <<endl;
}
string *m_strName;
};
void doSpeak(const Animal &animal)
{
animal.speak();
}
int main()
{
/*重写虚函数,执行子类函数;
若不使用虚函数,执行则问父类函数;*/
Cat cat;
doSpeak(cat);
system("puase");
return 0;
}