C++初阶之类和对象

C++初阶之类和对象

  • 1、封装
  • 2、对象的初始化和清理
    • 2.1 构造函数和析构函数
    • 2.2 构造函数的分类及调用
    • 2.3 拷贝构造函数的调用时机
    • 2.4 构造函数的调用规则
    • 2.5 深拷贝与浅拷贝
    • 2.6 初始化列表
    • 2.7 类对象作为类成员
    • 2.8 静态成员
  • 3、C++对象模型和this指针
    • 3.1 成员变量和成员函数分开存储
    • 3.2 this指针
    • 3.3 空指针访问成员函数
    • 3.4 const修饰成员函数
  • 4、友元
  • 5、运算符重载
  • 6、继承
    • 6.1 继承方式
    • 6.2 继承中的对象模型
    • 6.3 继承中构造和析构顺序
    • 6.4 继承同名成员处理方式
    • 6.5 继承同名静态成员处理方式
    • 6.6 多继承语法
    • 6.7 菱形继承
  • 7、多态
    • 7.1 纯虚函数和抽象类
    • 7.2 虚析构和纯虚析构

C++面向对象的三大特点:封装、继承和多态。C++认为万事万物均为对象,对象上有其属性和行为。

1、封装

封装为C++面向对象三大特性之一
封装的意义
意义1、将属性和行为作为一个整体表现事物

语法: class 类名{访问权限:属性/行为};

我们可以通过一个类实例化一个对象

 class Circle
 {
 public:
 	m_r;
 }void main()
 {
 	Circle c1;
 	c1.m_r=10;
 }

类中的行为和属性,我们统称为成员。 属性我们一般称为成员属性、成员变量;行为我们一般称为成员函数、成员方法。
意义2、类在设计时可以把属性和行为放在不同的权限下加以控制。
访问权限

名称 权限
公有权限 Public 类内类外均可访问
保护权限 Protected 类内可以访问,类外访问不到 (儿子可以访问父类中的保护内容)
私有权限 Privat 类内可以访问,类外访问不到 (儿子不可以访问父类中的私有内容)

这里的儿子、父亲为友元那里的内容,后续博主会更新到。

struct和class的区别

唯一的区别在于默认访问权限不同。
struct默认访问权限为公共权限,class默认访问权限为私有权限	

2、对象的初始化和清理

2.1 构造函数和析构函数

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数主要作用是对象销毁前系统自动调用,执行析构函数进行清理工作。
构造函数语法:类名(){}

析构函数语法:~类名(){}
构造函数与析构函数的特点
1、构造、析构函数没有返回值也不用写void
2、构造、析构函数名称与类名相同,析构函数在名称前加 ~
3、构造函数可以有参数,可以重载;析构函数不可以有参数,不可重载
4、程序在调用对象时、销毁对象前会自动调用构造、析构函数,无需手动调用,而且只会调用一次。如果编写程序时没有提供构造析构函数,编译器会提供一个空实现的构造析构函数。

2.2 构造函数的分类及调用

构造函数有两种分类方式:

按参数分类:有参构造和无参构造(默认构造)
按类型分类:普通构造和拷贝构造

拷贝构造函数作用是将传入对象身上的属性拷贝到自身属性。语法: 类名(const 类名&对象){} 除拷贝构造以外其余均为普通构造。

#include

class Person
{
public:
	int m_age;
	Person(int age)
	{
		this->m_age =age ;
		cout<<"调用有参构造函数"<<endl;
	}
	Person(const Person &p1)	//拷贝函数语法
	{
		this->m_age =p1.m_age  ;
		cout<<"调用拷贝构造函数"<<endl;
	}

};
int main()
{	
	
	Person p1(10);
	Person p2(p1);
	system ("pause");
	return 0;
}

C++初阶之类和对象_第1张图片

构造函数的三种调用方式:

括号法
显示法
隐式转换法

括号法
Person p1;默认构造函数调用。调用默认构造时不要加(),否则编译器会认为是一个函数的声明
Person p2(10);有参构造函数调用
Person p3(p2);拷贝构造函数调用

显示法
Person p1;默认构造函数调用
Person p2=Person(10);有参构造函数调用
Person p3=Person(p2);拷贝构造函数调用

注意:p2=Person(10)中的Person(10)单独写为匿名对象,它的特点是:当前行执行结束后,系统会立即回收掉匿名对象。
不要利用拷贝构造函数初始化匿名对象,编译器会认为Person(p2)==Person p2;

隐式转换法
Person p1;默认构造函数调用
Person p2=10;即:p2=Person(10)有参构造函数调用
Person p3=p2;即:p3=Person(p2)拷贝构造函数调用

2.3 拷贝构造函数的调用时机

1、使用一个已经创建完的对象来初始化一个新对象;
2、值传递方式给函数参数传值
3、以值方式返回局部对象

2.4 构造函数的调用规则

默认情况下,C++编译器至少给一个类添加3个函数即:默认构造函数默认析构函数默认拷贝构造函数(对属性进行值拷贝)
如果用户定义了有参构造函数,C++不提供无参构造函数,提供默认拷贝构造函数;如果用户提供了了拷贝构造函数,C++不提供其他构造函数。

2.5 深拷贝与浅拷贝

浅拷贝:简单的赋值拷贝操作(会带来堆区内存重复释放的问题)

深拷贝:在堆区申请空间,重新进行拷贝操作

2.6 初始化列表

作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)……{}

2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员,在构造时对象成员类的对象,再构造自身,析构顺序与之相反。

2.8 静态成员

静态成员就是在成员变量和成员函数前加static,其分别称为静态成员变量和静态成员函数。
静态成员变量的特点:
1、所有对象共享同一份数据
2、在编译阶段分配内存
3、类内声明,类外初始化
静态成员函数特点:
1、所有对象共享同一个函数
2、静态成员函数只能访问静态成员变量
静态成员的两种访问方式
1、通过对象访问 p.m_a;
2、通过类名访问 Person::m_a;
静态成员也有访问权限,类外访问不到私有静态成员。

3、C++对象模型和this指针

3.1 成员变量和成员函数分开存储

只有非静态成员变量才属于类的对象上。C++编译器会给每一个空对象也分配一个字节的空间,是为了区分空对象占内存的位置。即:每个空对象都有独一无二的内存地址。

3.2 this指针

this指针本质为指针常量。this指针指向被调用的成员函数所属的对象。是隐含在每一个非静态成员函数内的一种指针,它不需要定义,直接使用即可。
用途
1、当形参和成员变量重名时,可以用this指针来区分。
2、在类的非静态成员函数中返回对象本身,可用 return *this;

3.3 空指针访问成员函数

C++中空指针可调用成员函数,但要注意有没有用到this指针,如果用到this指针,需要注意代码的健壮性。可在成员函数里加if (this==NULL){return;}

3.4 const修饰成员函数

常函数:成员函数后加const,常函数内不可修改成员属性。但是成员属性声明时加关键字mutable后,在常函数中依旧可以修改。常对象只能调用常函数。

4、友元

友元的关键字friend
友元的实现:全局函数做友元,类做友元,成员函数做友元

5、运算符重载

运算符重载的概念:对已有的运算符重新进行定义,赋予另一种功能,以适应不同的数据类型。

加号运算符重载
左移运算符重载
递增运算符重载
赋值运算符重载
关系运算符重载
函数调用运算符重载

6、继承

面向对象三大特性之一
继承的好处:减少重复代码,语法:class 子类:继承方式 父类{};子类也称为派生类,父类也成为基类。

6.1 继承方式

继承方式有三种:公有继承、保护继承、私有继承

6.2 继承中的对象模型

C++初阶之类和对象_第2张图片父类中的所有非静态成员属性都会被子类继承,父类中的私有成员属性是被编译器给隐藏了,一次访问不到,但是确实被继承下去了。

6.3 继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数。其顺序是:先构造父类,在构造子类,析构顺序与构造顺序相反。

6.4 继承同名成员处理方式

问题:当子类与父类出现同名的成员,如何通过子类对象,访问到父类或父类中同名的数据呢?
访问子类同名成员,直接访问即可。s.m_a;
访问父类同名成员,需要加作用域。s.Base::m_a;

6.5 继承同名静态成员处理方式

问题:当子类与父类出现同名的静态成员,如何通过子类对象,访问到父类或父类中同名的数据呢?

通过子类对象,访问到父类或父类中同名的静态成员变量
再次回顾静态成员变量的特点:

1、所有对象共享同一份数据
2、在编译阶段分配内存
3、类内声明,类外初始化

静态成员变量有两种访问方式:1、通过对象访问;2、通过类名访问。
通过对象访问

通过类名访问

cout<<"Bsae 下 m_a"<<Son::Base::m_a<<endl;

第一个::代表通过类名方式访问,第二个::代表访问父类作用域下的成员变量
通过子类对象,访问到父类或父类中同名的静态成员函数

6.6 多继承语法

C++中允许一个类继承多个类。
语法:class 子类:继承方式 父类1,继承方式 父类2……{};
多继承中如果父类出现同名情况,子类使用时要加作用域。

6.7 菱形继承

菱形继承的概念:

两个派生类继承同一个基类,又有某个类同时继承这两个派生类,
这种继承被称为菱形继承或者钻石继承

下面是一个典型的菱形继承的例子

动物
羊驼

当菱形继承,两个父类拥有相同数据,需要加以作用域区分
相同的数据只要有一份就可以,菱形继承导致数据有两份,造成的资源浪费问题又该如何解决?
利用虚继承解决菱形继承的问题
在继承前 加上关键字virtual变成虚继承
Animal类称为虚基类

class Sheep: virture public Animal{}; 
class Tuo: virture public Animal{}; 
class SheepTuo: public Sheep{},public Tuo{}; 

7、多态

面向对象三大特性之一
多态分为静态多态和动态多态两类。函数重载和运算符重载属于静态多态,派生类和虚函数实现运行时多态为动态多态
两者的区别:静态多态的地址早绑定,编译阶段确定函数地址;动态多态的函数地址晚绑定,运行阶段确定函数地址。
动态多态要满足条件:1、有继承关系;2、子类重写父类虚函数
动态多态的使用:让父类的指针或者引用执行子类对象。
引用调用

class Animal
{
public:
   virtual void speak(){}
};
class Cat:public Animal
{
public:
    void speak()
   {
   	cout<<"猫在说话"<<endl;
   }
};
void Speak(Animal &animal)  // Animal &animal=cat
{
   animal.speak();
}
void main()
{
   Cat cat;
   Speak(cat);
}

指针调用

class Animal
{
public:
	virtual void speak(){}
};
class Cat:public Animal
{
public:	 
	 void speak()
	{
		cout<<"猫在说话"<<endl;
	}
};

void main()
{
	Animal *animal=new Cat;	
	animal ->speak ();
}

当父类没有虚函数时,Animal类内部结构 字节长度为1
当父类有虚函数时,Animal类内部结构 字节长度为4(指针)

class Animal
{
	virtual void speak()
	{
		cout<<"动物在说话"<<endl;
	}
};

C++初阶之类和对象_第3张图片
子类不发生重写
C++初阶之类和对象_第4张图片
子类发生重写父类虚函数
子类中的虚函数表 内部会替换成子类的虚函数地址
C++初阶之类和对象_第5张图片
多态的优点:

组织结构清晰
可读性强
对于前期和后期扩展和维护性高

7.1 纯虚函数和抽象类

7.2 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时不会调用到子类的析构函数,会出现内存泄露情况。

解决方案: 将父类中的析构函数改为虚析构或者纯虚析构

你可能感兴趣的:(《C++初阶》,c++,开发语言)