【嵌入式——C++】 类和对象
概念
C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程序设计。类是 C++ 的核心特性,通常被称为用户定义的类型。
类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。类中的数据称为成员变量,函数称为成员函数。
类的定义
class classname {
访问权限修饰符 public/private/protected
变量;
方法(){}
}
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
对象的定义
对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
Box Box1;
Box Box2;
成员函数
声明及定义
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
//成员函数可以在类内部定义
double getVolume(void)
{
return length * breadth * height;
}
};
调用成员函数
Box myBox; // 创建一个对象
myBox.getVolume(); // 调用该对象的成员函数
类访问修饰符
public
公有成员在程序中类的外部是可访问的,可以不使用任何成员函数来设置和获取公有变量的值。
private
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的,默认情况下,类的所有成员都是私有的。
protected
受保护的成员变量或函数与私有成员十分相似,但有一点不同,protected成员在子类中是可访问的。
继承中的特点
- public继承 基类public成员,protected成员,private成员的访问属性在子类中分别变成,public,protected,private;
class B : public A{}
- protected继承 基类public成员,protected成员,private成员的访问属性在子类中分别变成 protected,protected,private;
class B : protected A{}
- private继承 基类public成员,protected成员,private成员的访问属性在子类中分别变成 private,private,private。
class B : private A{}
类的构造函数(初始化)
类的构造函数是类的一种特殊的成员函数,它会每次创建类的新对象时执行。
- 构造函数的名称与类的名称是完全相同的;
- 不会返回任何类型,也不会返回void;
- 构造函数可用于为某些成员变量设置初始值;
- 构造函数可以有参数,因此可以发生重载;
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数
Line(double len); //带参构造函数
private:
double length;
};
//构造函数定义
Line::Line(void)
{
cout << "Object is being created" << endl;
}
//带参构造函数定义
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
使用初始化列表来初始化字段(两种写法是相同的)
方法一
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
方法二
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
类的析构函数(清理)
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。如果自己不提供,编译器会提供一个空实现的构造和析构。
- 析构函数的名称与类的名称是完全相同的,只是在前面加了个破浪号(~)作为前缀;
- 它不会返回任何值,也不能带有任何参数;
- 析构函数不可以有参数,因此不可以发生重载;
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次;
//析构函数声明
~Line();
//析构函数定义
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。如果在类中没有定义拷贝构造函数,编译器会自行定义一个,如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象;
void test01() { Person p1(20); Person p2(p1); }
- 复制对象把它作为参数传递给函数;
void doWork(Person p) { } void test02() { Person p; doWork(p); }
- 复制对象,并从函数返回这个对象。
Person doWork2() { Person p1; return p1; }
classname (const classname &obj) {
// 构造函数的主体
}
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
深拷贝和浅拷贝
深拷贝
在堆区重新申请空间,进行拷贝操作。
m_Height = new int(*p.m_Height)
浅拷贝
简单的赋值拷贝操作,带来的问题就是堆区内存重复释放。
m_Height = p.m_Height
友元函数
类的友元函数是定义在类外部,但是有权访问类的所有私有成员和保护成员,尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
声明函数为一个类的友元,需要在类定义中在该函数前使用关键字friend。
类做友元
class Building{
friend class GoodGay;
}
class GoodGay{}
成员函数做友元
class Building{
friend void GoodGay::visit();
}
class GoodGay{
void visit();
}
全局函数做友元
class Box
{
double width;
public:
double length;
friend void printWidth( Box box );
void setWidth( double wid );
};
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
内联函数
内联函数是通常与类一起使用,如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用函数的地方,对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前放置关键字inline,在调用函数之前需要对函数进行定义。在类定义中定义的函数都是内联函数,即使没有使用inline说明。
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
this指针
this指针是一个特殊的指针,指向当前对象的实例,每一个对象都能通过this指针来访问自己的地址,this是一个隐藏的指针,可以在类的成员函数中使用,它可以用来指向调用对象,当一个对象的成员函数被调用时,编译器会隐士地传递该对象的地址作为this指针,友元函数没有this指针,因为友元不是类的成员,只有成员函数才有this指针。
class MyClass {
private:
int value;
public:
void setValue(int value) {
this->value = value;
}
void printValue() {
std::cout << "Value: " << this->value << std::endl;
}
};
Person& PersonAddAge(Person& p) {
this->m_Age += p.m_Age;
return *this;
}
Person p1(10);
Person p2(10);
p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1); //40
Person(int age) {
//this指针指向被调用的成员函数所属的对象
this->m_Age = age;
}
指向类的指针
一个指向类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 -> ,就像访问指向结构的指针一样。
Box *ptrBox;
// 保存第一个对象的地址
ptrBox = &Box1;
// 现在尝试使用成员访问运算符来访问成员
cout << "Volume of Box1: " << ptrBox->Volume() << endl;
类的静态成员
使用static关键字来把成员定义为静态的,当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。静态成员在类的所有对象中是共享的,如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符**:😗*来重新声明静态变量从而对它进行初始化。
class Box
{
public:
static int objectCount;
}
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
静态成员函数
- 如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来,静态成员函数即使在类对象不存在的情况下也能被调用;
- 静态函数只要使用类名加范围解析运算符::就可以访问;
- 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数;
- 静态成员函数有一个类范围,他么不能访问类的this指针。
静态成员函数与普通成员函数的区别:
- 静态成员函数没有 this 指针,只能访问静态成员 ;
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有this 指针。
struct和class区别
- struct和class唯一的区别就在于默认的访问权限不同;
- struct默认权限为公共;
- class默认权限为私有。
运算符重载
对已有的运算符重新定义,赋予其另一种功能,以适应不同的数据类型。
+号重载
成员函数重载+号
Person operator+(Person& p) {
Person temp;
temp.m_A = this->m_A + p.m_A;
temp.m_B = this->m_B + p.m_B;
return temp;
}
全局函数重载+号
Person operator+(Person& p1, Person& p2) {
Person temp;
temp.m_A = p1.m_A + p2.m_A;
temp.m_B = p1.m_B + p2.m_B;
return temp;
}
Person p1;
p1.m_A = 10;
p1.m_B = 10;
Person p2;
p2.m_A = 10;
p2.m_B = 10;
Person p3 = p1 + p2;
左移运算符重载<<
全局函数重载左移运算符<<
ostream & operator<<(ostream &cout, Person &p) {
cout << "m_A" << p.m_A << "m_B" << p.m_B;
return cout;
}
递增运算符重载
前置递增
//成员函数重载前置++运算符 返回引用是为了一直对一个数进行递增
MyInteger& operator++() {
m_Num++;
return *this;
}
后置递增
//成员函数重载后置++运算符 int代表站位参数,用于区分前置和后置 必须是int
MyInteger operator++(int) {
MyInteger temp = *this;
m_Num++;
return temp;
}
<<重载
ostream& operator<<(ostream& cout, MyInteger myint) {
cout << myint.m_Num;
return cout;
}
赋值运算符重载
//重载赋值运算符
Person1& operator=(Person1& p) {
//先判断是否有属性在堆区,如果有先释放干净,然后再深拷贝
if (m_Age != NULL) {
delete m_Age;
m_Age = NULL;
}
m_Age = new int(*p.m_Age);
return *this;
}
关系运算符重载
//重载关系运算符==
bool operator==(Person2& p) {
if (this->m_Name == p.m_Name && this->m_Age == p.m_Age) {
return true;
}
return false;
}
函数调用运算符重载
class Myprint {
public:
void operator()(string text) {
cout << text << endl;
}
};
void test07() {
Myprint myFunc;
myFunc("hello world");
}
继承
继承的好处就是减少重复代码,
语法
class 子类 : 继承方式 父类
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
继承方式
class A {
public :
int a;
protected:
int b;
private:
int c;
};
公共继承
class B : public A {
public:
int a;
protected:
int b;
//不可访问
int c;
};
保护继承
class B : protected A {
protected:
int a;
int b;
//不可访问
int c;
};
私有继承
class B : private A {
private:
int a;
int b;
//不可访问
int c;
};
继承中的对象模型
父类中的所有非静态成员属性都会被子类继承,父类中私有成员属性,是被编译器给隐藏了,因此是访问不到,但是确实被继承下去了。
继承中的构造和析构的顺序
父构造-子构造-子析构-父析构
继承同名成员处理方式
访问子类同名成员,直接访问即可;
访问父类同名成员,需要加作用域
s.Parent::m_A; s.Parent::func();
如果子类中出现和父类同名的成员函数,子类的同名成员会隐藏掉父类中所有同名成员函数,如果想访问到父类中被隐藏的同名成员函数,需要加作用域。
多继承语法
class 子类: 继承方式 父类1,继承方式 父类2 ...
class C:public A ,public C{}
菱形继承
两个派生类继承同一个基类,又有某个类同时继承着两个派生类,这种继承被称为菱形继承,或者钻石继承。
多态
静态多态
函数重载和运算符重载属于静态多态,复用函数名。
动态多态
派生类和虚函数实现运行时多态。
静态多态和动态多态区别
- 静态多态的函数地址早绑定-编译阶段确定函数地址;
- 动态多态的函数地址晚绑定-运行阶段确定函数地址。
代码示例
父类函数必须加virtual关键字。
class Animal {
public:
//virtual 加上该关键字之后变成虚函数
virtual void speak() {
cout << "动物在说话" << endl;
}
};
class Cat : public Animal {
public:
void speak() {
cout << "小猫在说话" << endl;
}
};
class Dog : public Animal {
public:
void speak() {
cout << "小狗在说话" << endl;
}
};
void doSpeak(Animal& animal) {
animal.speak();
}
void test01() {
Cat cat;
doSpeak(cat);
Dog dog;
doSpeak(dog);
}
int main() {
test01();
system("pause");
return 0;
}
纯虚函数和抽象类
在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容,因此可以将虚函数改为纯虚函数。当类中有了纯虚函数,这个类也称为抽象类,
纯虚函数语法
virtual 返回值类型 函数名 (参数列表) = 0;
抽象类特点
- 无法实例化对象;
- 子类必须重写抽象类中的纯虚函数,否则也属于抽象类。
虚析构和纯虚析构
- 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码;
- 解决方式:将父类中的析构函数改为虚析构或者纯虚析构。
虚析构和纯虚析构共性
- 可以解决父类指针释放子类对象;
- 都需要有具体的函数实现。
虚析构和纯虚析构区别
- 如果是纯虚析构,该类属于抽象类,无法实例化对象;
- 虚析构语法:virtual ~Animal(){};
- 纯虚析构语法:virtual ~Animal()=0;;