C++面向对象最基本总结

C++面向对象最基本总结,第1张

补充:内存分区模型

栈:由编译器管理分配和回收,存放局部变量和函数参数。
堆:由程序员管理,需要⼿动 new malloc delete free 进⾏分配和回收,空间较⼤,但可能会出现内存泄漏和空闲碎⽚的情况。
全局/静态存储区:分为初始化和未初始化两个相邻区域,存储初始化和未初始化的全局变量和静态变量。
常量存储区:存储常量,⼀般不允许修改。
代码区:存放程序的⼆进制代码。

C++程序在执行时,将内存大方向划分为4个区域
代码区:存放函数体的二进制代码,由 *** 作系统进行管理的
全局区:存放全局变量和静态变量以及常量
栈区:由编译器自动分配释放, 存放函数的参数值,局部变量等
堆区:由程序员分配和释放,若程序员不释放,程序结束时由 *** 作系统回收

内存四区意义:
不同区域存放的数据,赋予不同的生命周期, 给我们更大的灵活编程
总结:
C++中在程序运行前分为全局区和代码区
代码区特点是共享和只读
全局区中存放全局变量、静态变量、常量
常量区中存放 const修饰的全局常量 和 字符串常量

函数重载

作用:函数名可以相同,提高复用性。体现 编译时多态
函数重载满足条件:
同一个作用域下(同一个类中的同名函数,是 重载)
函数名称相同
函数参数类型不同 或者 个数不同 或者 顺序不同(不关心返回值类型)

1、类和对象 1 简述一下什么是面向对象

面向对象是一种编程思想,把一切东西看成是一个个对象,把这些对象拥有的属性变量和 *** 作这些属性变量的函数打包成一个类来表示

面向过程和面向对象的区别
面向过程:根据业务逻辑从上到下写代码
面向对象:将数据与函数绑定到一起,进行封装,加快开发程序,减少重复代码的重写过程。
举例子
windows socket类 窗口类
点云类 PointCloud ,opencv
cv::Point2i, cv::Point2f, cv::Point2d, cv::Point3i, cv::Point3f, cv::Point3d.
面向对象的三大特征是封装、继承、多态。

封装:将数据和 *** 作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互。封装本质上是一种管理,不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访问。

继承:可以使用现有类的所有数据成员和函数成员功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。

多态:用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。实现多态,有二种方式,重写,重载。

C++认为万事万物都皆为对象,对象上有其属性和行为

//圆周率
# PI 3.14
const double PI = 3.14;
//1、封装的意义
//将属性和行为作为一个整体,用来表现生活中的事物
//封装一个圆类,求圆的周长
//class代表设计一个类,后面跟着的是类名
class Circle
{
//访问权限,默认 私有的
//属性
int m_r;//半径
//行为
//获取到圆的周长
public:
double calculateZC()
{
//2 * pi
* r
//获取圆的周长
return
2 * PI * m_r;
}
};
int main() {
//通过圆类,创建圆的对象
// c1就是一个具体的圆
Circle c1;
c1.m_r = 10; //给圆对象的半径 进行赋值 *** 作
//2 * pi * 10 = = 62.8
cout << "圆的周长为: " << c1.calculateZC() << endl;system("pause");
return 0;
}
构造函数和析构函数 对象的初始化和清理

构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手
动调用。
析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作。

c++利用了构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。

编译器提供的构造函数和析构函数是空实现。

空类A sizeof( A) 会实例化一个A类型的对象,为了确定唯一地址,返回1个字节
只含有 普通成员函数 或 析构函数,跟构造函数 还是返回1

类内的普通成员函数不参与sizeof()的统计。析构函数,跟构造函数这些成员函数,是跟sizeof无关的,也不难理解因为我们的sizeof是针对实例,而普通成员函数,是针对类体的,一个类的成员函数,多个实例也共用相同的函数指针,所以自然不能归为实例的大小。

空类不提供构造和析构,编译器会提 供 编译器提供的构造函数和析构函数是空实现

默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
还会提供一个 赋值运算符

构造函数调用规则如下:
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
如果用户定义拷贝构造函数,c++不会再提供其他构造函数

C++中的空类默认产生哪些类成员函数 c++编译器至少给一个类添加6个函数

1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认复制构造函数,对属性进行值拷贝(浅拷贝)

4.赋值运算符重载函数,对属性进行值拷贝(浅拷贝)
5.取值运算符重载函数
6.const取值运算符重载函数

如果类中有属性指向堆区(如类有一个指针成员),做赋值 *** 作时也会出现深浅拷贝问题

class Empty
{
public:
Empty(); // 缺省构造函数//
Empty( const Empty& ); // 拷贝构造函数//
~Empty(); // 析构函数//
Empty& operator=( const Empty& ); // 赋值运算符//
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};

只有当你需要用到这些函数的时候,编译器才会去定义它们?

深拷贝与浅拷贝

*典型例子,类中含有一个指针成员 int p;
这就是必要的时候 必须手动的写拷贝构造函数

浅拷贝:简单的赋值拷贝 *** 作。只拷贝一个指针变量p 的拷贝
可能会导致同一个指针被释放2次
深拷贝:在堆区重新申请空间,拷贝指针p指向的对象。

https://blog.csdn.net/wue1206/article/details/81138097

class Test
{
private:
    int* p;
public:
    Test(int x)
    {
        this->p=new int(x);
        cout << "对象被创建" << endl;
    }
    ~Test()
    {
        if (p != NULL)
        {
            delete p;
        }
        cout << "对象被释放" << endl;
    }
    int getX() { return *p; }
    //深拷贝(拷贝构造函数)
    Test(const Test& a)
    {
        this->p = new int(*a.p);
        cout << "对象被创建" << endl;
    }
    //浅拷贝(拷贝构造函数)
    //Test(const Test& a)
    //{
    //  this->p = a.p;
    //  cout << "对象被创建" << endl;
    //}
};

int main()
{
    Test a(10);
    //我们手动的写拷贝构造函数,C++编译器会调用我们手动写的
    Test b = a;
    return 0;
}

初始化列表
Person p(1, 2, 3);
p.PrintPerson();
类对象作为类成员,考察构造析构顺序

https://blog.csdn.net/weixin_39731083/article/details/81903997
2. 构造函数的调用顺序
基类构造函数、对象成员构造函数、派生类本身的构造函数

  1. 析构函数的调用顺序
    派生类本身的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反)

4. 特例
局部对象,在退出程序块时析构

静态对象,在定义所在文件结束时析构

全局对象,在程序结束时析构

继承对象,先析构派生类,再析构父类

对象成员,先析构类对象,再析构对象成员

4.2.8 静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员

//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到

静态成员分为:
静态成员变量
所有对象共享同一份数据
在编译阶段分配内存
类内声明,类外初始化
静态成员函数
所有对象共享同一个函数,可以通过类名 或者 对象名调用
静态成员函数只能访问静态成员变量,并且静态成员函数 不能返回 对象本身

4.3 C++对象模型和this指针 在C++中,类内的成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储
(成员函数的代码应该反正代码区??)

只有非静态成员变量才属于类的对象的内存上,占用对象的内存空间
this指针概念

因为C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢? this指针
this指针 非静态成员函数通过this指针来区分是那个对象在调用自己。
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可

显式的this指针的用途:
1、当形参和成员变量同名时,可用this指针来区分
2、在类的非静态成员函数中返回对象本身,可使用return *this

class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
Person& PersonAddPerson(Person p)
{
this->age += p.age;
//返回对象本身
return *this;
}
int age;
};
void test01()
{
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;
	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}
4.3.4 const修饰成员函数

const没有地方了,函数前面的位置被static/inline 占用,只能放在 函数后面,O(∩_∩)O哈哈~

常函数:
成员函数后加const后我们称为这个函数为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中依然可以修改
常对象:
声明对象前加const称该对象为常对象
常对象只能调用常函数

class Person {
public:
	Person() {
		m_A = 0;
		m_B = 0;
	}

	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const {
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的

		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
	}

	void MyFunc() const {
		//mA = 10000;
	}

public:
	int m_A;
	mutable int m_B; //可修改 可变的
};


//const修饰对象  常对象
void test01() {

	const Person person; //常量对象  
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量

	//常对象访问成员函数
	person.MyFunc(); //常对象不能调用const的函数

}

int main() {

	test01();

	system("pause");

	return 0;
}
**4.4 友元 friend **

友元只是一个声明,完全不可能是这个类的成员

友元的目的 就是把一个函数或者类 声明为本类的友元,
他们就可以访问本类中的私有成员

友元的三种实现
全局函数做友元
类做友元
成员函数做友元

friend void goodGay(Building * building);
friend class goodGay;
friend void goodGay::visit();
4.5 运算符重载

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

4.6 继承

C++中的类,有三种访问权限(也称作访问控制),它们分别是public、protected、private


概念一:静态的
在C++中一个类的成员的访问级别可分为public,protected,和private。public的访问级别最低,谁都可以访问。
一个类的public成员变量、成员函数,可以通过类的成员函数、类的实例变量进行访问
protected访问级别中等,一个类的protected成员变量、成员函数,无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。
private访问级别最高, 一个类的private成员变量、成员函数,无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。
到这里我们发现似乎protectd和private没有什么太大的区别,但是主要区别在概念二中。
概念二:动态的
关于动态的,也就是动作的意义。其中包含父子继承的关系。也就是自类是如何继承父类的,是public继承,protected继承,还是private继承。
(1)public继承
派生类通过public继承,基类的各种权限不变 。
派生类的成员函数,可以访问基类的public成员、protected成员,但是无法访问基类的private成员。
派生类的实例变量,可以访问基类的public成员,但是无法访问protected、private成员,仿佛基类的成员之间加到了派生类一般。
可以将public继承看成派生类将基类的public,protected成员囊括到派生类,但是不包括private成员。
(2)proteced继承
派生类通过protected继承,基类的public成员在派生类中的权限变成了protected 。protected和private不变。
派生类的成员函数,可以访问基类的public成员、protected成员,但是无法访问基类的private成员。
派生类的实例变量,无法访问基类的任何成员,因为基类的public成员在派生类中变成了protected。
可以将protected继承看成派生类将基类的public,protected成员囊括到派生类,全部作为派生类的protected成员,但是不包括private成员。
private成员是基类内部的隐私,除了友元,所有人员都不得窥探。派生类的友元,都不能访问
(3)private继承
派生类通过private继承,基类的所有成员在派生类中的权限变成了private。
派生类的成员函数,可以访问基类的public成员、protected成员,但是无法访问基类的private成员。
派生类的实例变量,无法访问基类的任何成员,因为基类的所有成员在派生类中变成了private。
可以将private继承看成派生类将基类的public,protected成员囊括到派生类,全部作为派生类的private成员,但是不包括private成员。
private成员是基类内部的隐私,除了友元,所有人员都不得窥探。派生类的友元,都不能访问

继承的好处:==可以减少重复的代码
class A : public B; 

A 类称为子类 或 派生类
B 类称为父类 或 基类

派生类中的成员,包含两大部分:
一类是从基类继承过来的,一类是自己增加的成员。
从基类继承过过来的表现其共性,而新增的成员体现了其个性。

构造析构顺序

https://blog.csdn.net/weixin_39731083/article/details/81903997
2. 构造函数的调用顺序
基类构造函数、对象成员构造函数、派生类本身的构造函数
3. 析构函数的调用顺序
派生类本身的析构函数、对象成员析构函数、基类析构函数(与构造顺序正好相反)

4.6.5 继承同名成员处理方式 Overload(重载 ) Override(覆盖) (虚函数, 重写???) 3. Overwrite(改写)

https://www.cnblogs.com/kuliuheng/p/4107012.html
1、Overload(重载)
重载的概念最好理解,在同一个类声明范围中(在同一个作用域),定义了多个名称完全相同、参数(类型或者个数)不相同的函数,就称之为Overload(重载)。重载的特征如下:
(1)相同的作用域范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

2、Override((虚函数, 重写???)
Override的概念其实是用来实现C++多态性的,即 虚函数
在子类重新改写父类声明为virtual的函数,在程序运行的时候,根据对象的类型来判断应该调用父类还是之类中的同名函数。
典型的例子,在父类中,一般需要声明析构函数为virtual 函数。避免内存泄漏
因为程序中,使用父类类型的指针指向子类对象,必须声明父类析构函数为virtual 函数。

Override(覆盖)的特征如下:
(1)不同的作用域(分别位于派生类与基类);
(2)函数名字相同;
(3)参数列表完全相同;
(4)基类函数必须有virtual 关键字。

3. Overwrite(改写)
父类中函数没有声明为虚函数,子类中改写了这个同名函数

改写是指派生类的函数屏蔽(或者称之为“隐藏”)了与其同名的基类函数。正是这个C++的隐藏规则使得问题的复杂性陡然增加,这里面分为两种情况讨论:

(1)如果派生类的函数与基类的函数同名,但是参数不同。那么此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。那么此时,基类的函数被隐藏(注意别与覆盖混淆)。

class Base{
public:
    virtual int fcn();
};
class Derived1:public Base{
public:
    //隐藏基类的fcn,这个fcn不是虚函数
    //Derived1继承了Base::fcn()的定义
    int fcn(int);         //形参类别与Base中的fcn不一样
    virtual void f2();    //是一个新的虚函数,在Base中不存在
};
class Derived2:public D1{
publicint fcn(int);        //是一个非虚函数,隐藏了Derived1::fcn(int)
    int fcn();           //覆盖了Base的虚函数fcn
    void f2();           //覆盖了Derived1的虚函数f2
4.6.6 继承同名静态成员处理方式

问题:继承中同名的静态成员在子类对象上如何进行访问?
静态成员和非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加上 父类的 作用域
//同名成员属性
void test01()
{
	//通过对象访问
	cout << "通过对象访问: " << endl;
	Son s;
	cout << "Son  下 m_A = " << s.m_A << endl;
	cout << "Base 下 m_A = " << s.Base::m_A << endl;

	//通过类名访问
	cout << "通过类名访问: " << endl;
	cout << "Son  下 m_A = " << Son::m_A << endl;
	cout << "Base 下 m_A = " << Son::Base::m_A << endl;
}
4.6.7 多继承语法

C++允许一个类继承多个类
语法: class 子类 :继承方式 父类1 , 继承方式 父类2...
多继承可能会引发父类中有同名成员出现,需要加作用域区分
C++实际开发中不建议用多继承

//语法:class 子类:继承方式 父类1 ,继承方式 父类2 
class Son : public Base2, public Base1 
{
public:
	Son()
	{
		m_C = 300;
		m_D = 400;
	}
public:
	int m_C;
	int m_D;
};
4.6.8 菱形继承

注意:虚基类

//继承前加virtual关键字后,变为虚继承
//此时公共的父类Animal称为虚基类
class Sheep : virtual public Animal {};
class Tuo   : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};

总结:

  • 菱形继承带来的主要问题是子类继承两份相同的数据,导致资源浪费以及毫无意义
  • 利用虚继承可以解决菱形继承问题,保证只有一份 虚基类中的 数据
4.7 多态

多态是C++面向对象三大特性之一**
多态分为两类
静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名 (overload)
动态多态: 派生类和虚函数实现运行时多态 (override关键字配合 virtual虚函数使用)

override是C++11中的一个新的继承控制关键字。override确保在派生类中声明的重载函数跟基类的 virtual虚函数有相同的声明。

override明确地表示一个函数是对基类中一个虚函数的重载。更重要的是,它会检查基类虚函数和派生类中重载函数的签名不匹配问题。如果签名不匹配,编译器会发出错误信息。

override表示函数应当重写基类中的虚函数(用于派生类的虚函数中)

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编
总结
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象
4.7.3 纯虚函数和抽象类(或者叫纯虚类)

区别:抽象类(纯虚函数) 和 虚基类(虚继承)

在多态中,通常父类中虚函数的实现是毫无意义的,主要都是调用子类重写的内容
因此可以将虚函数改为纯虚函数
纯虚函数语法:virtual 返回值类型 函数名 (参数列表)= 0 ;
当类中有了纯虚函数,这个类也称为抽象类
抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Base
{
public:
	//纯虚函数
	//类中只要有一个纯虚函数就称为抽象类
	//抽象类无法实例化对象
	//子类必须重写父类中的纯虚函数,否则也属于抽象类
	virtual void func() = 0;
};
4.7.5 虚析构和纯虚析构 父类指针 指向 子类对象 带来的问题

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时会调用父类的析构函数代码,无法调用到子类的析构代码
解决方式:将父类中的析构函数定义改为虚析构或者纯虚析构
虚析构语法:
virtual ~类名(){}
纯虚析构语法:
virtual ~类名() = 0;
`类名::~类名(){}

​1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
3. 拥有纯虚析构函数的类也属于抽象类

class Animal {
public:
	Animal()
	{
		cout << "Animal 构造函数调用!" << endl;
	}
	virtual void Speak() = 0;
	//析构函数加上virtual关键字,变成虚析构函数
	//virtual ~Animal()
	//{
	//	cout << "Animal虚析构函数调用!" << endl;
	//}
	virtual ~Animal() = 0;
};

Animal::~Animal()
{
	cout << "Animal 纯虚析构函数调用!" << endl;
}

//和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能够被实例化。

class Cat : public Animal {
public:
	Cat(string name)
	{
		cout << "Cat构造函数调用!" << endl;
		m_Name = new string(name);
	}
	virtual void Speak()
	{
		cout << *m_Name <<  "小猫在说话!" << endl;
	}
	~Cat()
	{
		cout << "Cat析构函数调用!" << endl;
		if (this->m_Name != NULL) {
			delete m_Name;
			m_Name = NULL;
		}
	}

public:
	string *m_Name;
};

void test01()
{
	Animal *animal = new Cat("Tom");
	animal->Speak();

	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
	//怎么解决?给基类增加一个虚析构函数
	//虚析构函数就是用来解决通过父类指针释放子类对象
	delete animal;
}

int main() {

	test01();

	system("pause");

	return 0;
}
5 文件 *** 作 流对象

是面向对象的思维;
程序运行时产生的数据都属于临时数据,程序一旦运行结束都会被释放通过文件可以将数据持久化C++中对文件 *** 作需要包含头文件 < fstream >

文件类型分为两种:

  1. 文本文件 - 文件以文本的ASCII码形式存储在计算机中
  2. 二进制文件 - 文件以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们

*** 作文件的三大类:
3. ofstream:写 *** 作
4. ifstream: 读 *** 作
5. fstream : 读写 *** 作

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/3002391.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存