引言
小伙伴们都知道C++面向对象难,可是大家都知道,这个才是C++和C的真正区别的地方,也是C++深受所有大厂喜爱的原因,它的原理更接近底层,它的逻辑更好,但是学习难度高,大家一定要坚持下来呀,本章呢对于C++有关的知识开始讲解封装继承和多态。好了啦废话不多说,跟着小杨一起开始吧!
冲冲冲!!!!!!
封装
- 封装的关键字是clas。
- 从上一章当中,我们已经知道了,封装就是现实生活中的事物定义为类,将事物的数据抽象为属性,将事物的行为抽象为方法。
类的数据成员和成员函数
- 数据成员
- 类中的数据成员描述该类对象的属性,数据成员必须在类体中定义,其定义方式须与一般变量相同,但对于数据成员的访问要手访问控制权限的限定。
- 数据成员的初始化与普通变量的初始化形式有所不同,不能使用圆括号(即对象方式初始化)可以使用=和{ }。
- 多个数据成员之间不能重名,一个类是一个作用域。
- 成员函数
- 成员函数描述类对象的行为,即该类对象的所执行的操作。
- 一个类的多个同名不同参数类型的成员函数可以重载。
- 类的成员函数也可以是内联函数。
- 函数成员参数也可以有默认值。
- 成员的访问控制
- private私有访问权限
- 数据成员和成员函数只允许访问类本身的成员函数访问,对类的外部不可见。
- protected保护访问权限
- 数据成员和成员函数允许类本身及其派生类的成员函数访问
- public共有访问权限
- 数据成员和成员函数对外类外部可见,类内部也能访问
- private私有访问权限
类的定义代码示例:
#include <iostream>
using namespace std;
/*
class关键字定义类。
类名是标识符,需要满足标识符规范,类名命名规范是大驼峰,
每个单词首字母大写,其他字母小写。
类名后一对花括号表示类的作用域,也称为类体,分号表示类定义结束。
关键字private,protected,public称为访问控制机制。默认为private。
*/
class Rect
{
private:
// 属性
int m_length;
int m_width;
public:
/*
类中的成员函数(方法)可以再类中直接定义,也可以只写函数声明,然后在类的外面写出函数定义。
*/
// 方法
void setLength(int length);
// 函数成员可以内联
inline void setWidth(int width = 0);
int getArea();
int getPerimeter();
};
void Rect::setLength(int length)
{
m_length = length;
}
void Rect::setWidth(int width)
{
m_width = width;
}
int Rect::getArea()
{
return m_width * m_length;
}
int Rect::getPerimeter()
{
return 2 * (m_width + m_length);
}
int main()
{
// 定义(创建)Rect类的对象r。
Rect r, r1;
// .操作符访问成员,可以访问数据成员或成员函数。
r.setLength(2);
r.setWidth(3);
cout << r.getArea() << endl;
cout << r.getPerimeter() << endl;
// 多个对象之间的属性互相独立。
r1.setLength(1);
// 函数成员参数可以有默认值
r1.setWidth();
cout << r1.getArea() << endl;
cout << r1.getPerimeter() << endl;
return 0;
}
- 类的特殊成员
在类中,除了一些简单的数据成员和成员函数外,还有一些具有特殊作用,和特殊规范的函数,这些类的特殊成员正是封装的厉害之处,也是满足各种各样要求的一大利器,让我们一起来看一下吧。
构造函数
- 在创建对象时,利用特定的值构造对象,将对象初始化为一个特定状态。
- 构造函数也是类的成员函数,除具有一般成员函数的特点外,还有以下特点:
- 构造函数的函数名和类名相同
- 不能定义构造函数的类型,因为构造函数没有返回值,也就没有返回值类型。
- 构造函数不在程序中调用,在对象被创建时,被编译器调用,
- 构造函数可以被重载
- 如果类中没有构造函数,则在C++编译器中会默认自动生成一个无参的默认构造函数,一旦用户显示定义任何构造函数,默认构造函数将不再提供。
- 无论任何方式创建对象,公祖奥函数都会被调用,如果找不到和参数匹配的构造函数,编译器会产生错误。
- 初始化列表
- 除了在构造函数体中为数据成员初始化,还可以使用初始化列表对数据成员初始化。
- 类的常量成员需要初始化时,只能在初始化列表初始化。
- 构造函数代码示例:
#include <iostream>
using namespace std;
class Rect
{
private:
int m_length;
int m_width;
public:
/*
构造函数和类同名且没有返回类型。
构造函数允许重载。
没有定义构造函数时,编译器会提供默认的无参构造函数,当定义任何构造函数后,编译器不再提供默认构造函数。
*/
Rect();
Rect(int length, int width);
void setLength(int length);
inline void setWidth(int width = 0);
int getArea();
int getPerimeter();
};
Rect::Rect()
{
cout << "Rect::Rect()" << endl;
}
Rect::Rect(int length, int width)
{
cout << "Rect(int length, int width)" << endl;
m_length = length;
m_width = width;
}
void Rect::setLength(int length)
{
m_length = length;
}
void Rect::setWidth(int width)
{
m_width = width;
}
int Rect::getArea()
{
return m_width * m_length;
}
int Rect::getPerimeter()
{
return 2 * (m_width + m_length);
}
int main()
{
// 构造函数由编译器在创建对象时调用。
Rect r(2, 3);
Rect r1;
return 0;
}
析构函数
- 与构造函数相对的就是析构函数,在删除一个对象前被调用,释放该对象的内存空间及其他的一些清理工作。
- 析构函数的特征
- 析构函数的名字“~类名”
- 析构函数没有参数,也不能指定返回类型,一个类只有一个析构函数。
- 当一个类删除时,编译器会自动调用析构函数。
- 如果没有显示定义,编译器将默认生成一个默认的析构函数,函数体空。
- 析构函数代码示例:
#include <iostream>
using namespace std;
class Circle
{
private:
const float PI = 3.1415926;
int m_radius;
char* m_name = NULL;
public:
Circle(int radius);
Circle(const char* name, int radius);
// 声明析构函数
~Circle();
int getArea();
void info();
};
// 常量必须使用初始化列表初始化
Circle::Circle(const char* name, int radius)
{
int len = strlen(name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, name);
m_radius = radius;
}
Circle::Circle(int radius)
{
m_radius = radius;
}
Circle::~Circle()
{
cout << "Circle::~Circle()";
if(m_name != NULL)
{
cout << m_name;
delete[]m_name;
}
cout << endl;
}
int Circle::getArea()
{
return PI * m_radius * m_radius;
}
void Circle::info()
{
cout << "m_name: " << m_name << " m_radius:" << m_radius << endl;
}
int main()
{
// 动态分配的对象在使用delete删除时,调用析构函数
Circle* p = new Circle("HHH", 2);
delete p;
// 编译器在栈中创建的对象,在编译器删除对象时调用析构函数,先创建的对象后删除。
char c[20] = "test1";
Circle circle(c, 1);
cout << circle.getArea() << endl;
circle.info();
c[4] = '2';
circle.info();
Circle circle1(1);
return 0;
}
拷贝构造函数
- 拷贝构造函数可以实现用一个已存在的对象初始化新对象。
- 拷贝构造函数的一般格式为:类名(const 类名& 形参名)
- 拷贝构造函数(自定义)代码示例:
#include <iostream>
using namespace std;
// 自定义拷贝构造函数
class Circle
{
private:
const float PI = 3.1415926;
int m_radius;
char* m_name = NULL;
public:
Circle(const char* name, int radius);
/*
拷贝构造函数的参数相对固定,常见方式为:
(const 类名& 形参名)
*/
Circle(const Circle& c);
~Circle();
int getArea();
void info();
};
Circle::Circle(const char* name, int radius)
{
int len = strlen(name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, name);
m_radius = radius;
}
// 实现拷贝构造函数
Circle::Circle(const Circle& c)
{
int len = strlen(c.m_name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, c.m_name);
m_radius = c.m_radius;
}
Circle::~Circle()
{
ccountout << "Circle::~Circle()";
if (m_name != NULL)
{
cout << m_name;
delete[]m_name;
}
cout << endl;
}
int Circle::getArea()
{
return PI * m_radius * m_radius;
}
void Circle::info()
{
cout << "m_name: " << m_name << " m_radius:" << m_radius << endl;
}
int main()
{
Circle circle1("C1", 2);
circle1.info();
Circle circle2(circle1);
circle2.info();
return 0;
}
- 拷贝构造函数(默认)代码示例:
#include <iostream>
using namespace std;
// 使用默认拷贝构造函数
class Circle
{
private:
const float PI = 3.1415926;
int m_radius;
char* m_name = NULL;
public:
Circle(char* name, int radius);
void info();
};
Circle::Circle(char* name, int radius)
{
m_name = name;
m_radius = radius;
}
void Circle::info()
{
cout << "m_name: " << m_name << " m_radius:" << m_radius << endl;
}
int main()
{
char c[] = "C1";
Circle circle1(c, 2);
circle1.info();
Circle circle2(circle1);
circle2.info();
return 0;
}
赋值函数
-
用赋值语句将一个对象的值赋给了另一个已有同类对象时,将调用赋值函数。
-
赋值函数的一般格式:类名&operate=(const类名&形参名)
-
实现赋值函数时,一般要判断入参是否为对象本身,如果是对象本身,则不进行操作直接返回。
-
赋值函数代码示例:
#include <iostream>
using namespace std;
class Circle
{
private:
const float PI = 3.1415926;
int m_radius;
char* m_name = NULL;
public:
Circle(const char* name, int radius);
~Circle();
Circle& operator=(const Circle& c);
void info();
};
Circle::Circle(const char* name, int radius)
{
cout << "Circle::Circle(const char* name, int radius):" << name << endl;
int len = strlen(name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, name);
m_radius = radius;
}
Circle::~Circle()
{
count << "Circle::~Circle()";
if (m_name != NULL)
{
cout << m_name;
delete[]m_name;
}
cout << endl;
}
// 赋值函数的一般格式
Circle& Circle::operator=(const Circle& c)
{
cout << "Circle::operator=(const Circle& c):" << " c.m_name: " << c.m_name << endl;
// 赋值函数要判断实参是不是对象本身,如果是对象本身则直接返回。
if (this != &c)
{
if (m_name != NULL)
{
delete[] m_name;
}
int len = strlen(c.m_name) + 1;
m_name = new char[len];
strcpy_s(m_name, len, c.m_name);
m_radius = c.m_radius;
}
// this是对象的固有指针,指向对象本身。
return *this;
}
void Circle::info()
{
cout << "m_name: " << m_name << " m_radius:" << m_radius << endl;
}
int main()
{
Circle circle1("C11", 2);
circle1.info();
Circle circle2("C2", 3);
// 调用赋值函数
// circle2 = circle1;
circle2 = circle2;
circle2.info();
return 0;
}
继承
有关继承的知识点并不是很多,但是继承也是很重要的一环。但是其繁琐的理解的难易程度也不小。
- 子类继承父类的属性和方法,使得子类具备父类的特征。
- 继承的类型
- 单继承:子类只有一个父类(基类)
- 多继承:子类有多于一个父类(基类)
- 直接继承:顾名思义,就是他的继承来自他的父类
- 间接继承:他的继承是通过继承的子类做了父类的继承。
- 派生类不继承父类的构造方法(构造函数)每个子类都必须实现至少一个构造函数。
- 方法的隐藏:子类中定义和父类方法名完全相同时,子类的函数屏蔽掉了预期同名的所有父类函数,这叫方法的隐藏。
- 有关派生类继承的代码示例:
#include <iostream>
#include "Pet.h"
#include "Cat.h"
#include "Dog.h"
/*
我们在对应的头文件里加上了每个派生类的构造函数,还有私人属性和是实现方法的声明,。
同时我们在对应的cpp文件里对函数功能的具体实现。
*/
using namespace std;
int main()
{
Pet pet("小强", 20);
pet.info();
pet.barking();
pet.barking(3);
pet.running();
Cat cat("叮当", 10);
cat.info();
cat.barking();
cat.barking(3);
cat.running();
Dog dog("旺财", 10);
dog.info();
dog.barking();
// dog.barking(3); 父类Pet类的barking(int n)方法被隐藏了。
dog.running();
dog.guardHouse();
return 0;
}
- 类的继承方式
- 默认的继承方式是private。
- 派生类的构造和析构构成
- 派生类构造函数的执行顺序
- 调用基类的构造函数执行派生类的构造函数体。
- 派生类的析构函数执行顺序
- 执行子类的析构函数调用基类的析构函数
- 派生类构造函数的执行顺序
- 派生类的构造和析构构成代码示例:
#include <iostream>
using namespace std;
class Animal
{
protected:
int m_age;
public:
Animal( age)
{
cout << "Animal(int age): " << endl;
m_age = age;
}
~Animal()
{
cout << "~Animal() " << endl;
}
void info()
{
cout << "Animal: m_age: " << m_age << endl;
}
};
class Tiger : public Animal
{
private:
int m_weight;
public:
Tiger(int age, int weight) : Animal(age)
{
cout << "Tiger(int age, int weight) : Animal(age): " << endl;
m_weight = weight;
}
~Tiger()
{
cout << "~Tiger() " << endl;
}
void info()
{
cout << "Tiger: m_age: " << m_age << " m_weight: " << m_weight << endl;
}
};
int main()
{
Tiger tiger(2, 20);
return 0;
}
- 多继承简介
- 一个派生类可以有很多基类,称之为多继承。
- 多继承时,可以按照父类声明顺序构造父类,按照构造相反的顺序析构。
- 多继承代码示例:
#include <iostream>
using namespace std;
// 基类BaseA
class BaseA
{
protected:
int m_a;
int m_c = 100;
public:
BaseA(int a)
{
cout << "BaseA(int a)" << endl;
m_a = a;
}
~BaseA()
{
cout << "~BaseA()" << endl;
}
};
// 基类BaseB
class BaseB
{
protected:
int m_b;
int m_c = 200;
public:
BaseB(int b)
{
cout << "BaseB(int b)" << endl;
m_b = b;
}
~BaseB()
{
cout << "~BaseB()" << endl;
}
};
class Derived : public BaseA, public BaseB
{
public:
Derived(int a, int b) : BaseB(b), BaseA(a)
{
cout << "Derived(int a, int b) : BaseA(a), BaseB(b)" << endl;
}
~Derived()
{
cout << "~Derived()" << endl;
}
void info()
{
cout << "m_a: " << m_a << " m_b: " << m_b << endl;
// 当多个父类中出现同名成员时,使用 类名::成员名 访问。
cout << "BaseA::m_c: " << BaseA::m_c << " BaseB::m_c: " << BaseB::m_c << endl;
}
};
int main()
{
Derived derived, 20);
dervied.info();
return 0;
}
结语
由于篇幅有限,暂不能写完有关继承和多态的知识,剩下的就留着下一篇章,超详细的 C++中的封装继承和多态的知识总结<2.多态>,小伙伴们,虽然没有写完,但是本章的内容也不少,小伙伴们一定要认真复习,这个理解起来挺费劲的,但是也是我们学习后续必不可少的内容。小伙伴们加油呀~
冲冲冲!!!!