类和对象

it2024-08-22  51

文章目录

类和对象封装访问权限struct和class的区别 对象的初始化和清理构造函数与析构函数构造函数的分类及调用拷贝构造函数的调用时机构造函数的调用规则深拷贝与浅拷贝初始化列表类对象作为类成员静态成员静态成员变量静态成员函数 c++对象模型和this指针变量和成员函数分开存储this指针空指针访问成员函数const修饰成员函数 友元全局函数做友元类做友元成员函数做友元 运算符重载加号运算符重载 继承继承的基本语法继承方式继承中的对象模型继承中的构造和析构顺序继承同名成员的处理方式(重定义)菱形继承(钻石继承) 多态多态的基本概念多态的深入剖析纯虚函数和抽象类虚析构和纯虚析构

类和对象

c++面向对象的三大特性为:封装、继承和多态。

c++认为一切皆对象,对象有其属性和行为。

封装

以人为例子 属性:身高、体重、年龄。(一般使用变量) 行为:唱、跳、rap、篮球。(一般使用函数) 就是把属性和行为看做一个整体,对外只暴露接口

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

通过类可以创造一个具体的对象

类名 对象名;

例子:

class crice//定义一个圆类 { public: //设置访问权限 float pi = 3.14; int r; float getfun() { return pi * r*r; } }; int main() { crice c1;//给圆类定义一个对象c1 c1.r = 10;//把10传递给对象c1中的r cout << c1.getfun() << endl;//调用c1中的getfun函数 }

访问权限

访问权限有三类: ①公共权限:public(类内可访问,类外可访问) ②保护权限:protected(类内可访问,类外不可访问) ③私有权限:private(类内可访问,类外不可访问)

struct和class的区别

①struct的默认访问权限是公有 ②class的默认访问权限是私有

对象的初始化和清理

构造函数与析构函数

这两个函数会被编译器自动调用,完成对象的初始化和清理工作。 如果用户没有书写这两个函数,那么编译器会调用内部的提供的。 编译器内部提供的这两个函数都是空实现

构造函数:对象创建时自动调用 析构函数:对象销毁前自动调用

构造函数语法: 类名(){ }

class A { public: A()//构造函数,与类同名,可以有参数,会发生重载 { cout << "构造函数" <<endl; } }; int main() { A a1;//创建了一个对象 return 0}

析构函数语法: ~类名(){ }

class A { public: ~A()//析构函数,不能发生重载 { cout << "析构函数" <<endl; } }; int main() { A a1; return 0//程序结束时,a1的空间被销毁,调用析构函数 }

构造函数的分类及调用

两种分类方式: 按照参数分:有参构造和无参构造。 按照类型分:普通构造和拷贝构造。

拷贝构造函数: 类名(const 类名& 对象名) class crice { int r; //只有这种形式才叫拷贝构造,这里的参数必须是一个该类的引用 crice (crice& p) { r=p.r;//将传入人身上的所有属性拷贝到我身上, } }

调用方法

1.括号法 crice p1; //没有括号,默认构造函数调用 crice p2(10);//会调用有参构造函数 crice p3(p2);//参数为crice类,调用拷贝构造函数 crice p1();//这样写会被认为是一个函数的声明,不会调用构造函数 2.显示法 crice p1; crice p2 = crice(10);//有参构造 crice p2 = crice(p3);//拷贝构造 crice(10;//当前行结束后系统会自动回收掉无名对象,所以它的构造和析构都发生在这一行 3.隐式转化法 crice p4 = 10;//相当于crice p4(10),有参构造 crice p5 = p4;//相当于crice p5(p4),拷贝构造

拷贝构造函数的调用时机

1.使用已经创建完毕的对象来对象来初始化一个新对象

int main() { preson p1;//构造了一个对象。 person p2(p1)//使用了方法一 }

2.以值传递的方式给函数参数传值

void test(person p) { } int main { person p1; test(p1);//函数的调用为值的传递,使用了方法二,相当于peson p = p1; }

3.值方式返回局部对象

person test() { person p1; return p1; //p1是一个局部对象,返回的仅仅只是它的值,实际上创造了一个无名对象来接收它 }

构造函数的调用规则

默认情况下,c++编译器会给一个类默认添加三个函数 1.默认构造函数(空),2.默认析构函数(空)3.默认拷贝构造函数(进行值拷贝)

调用规则: 1.没有书写任何函数时,编译器会默认启用三个默认函数。 2.如果我们书写了有参构造,编译器不在启用无参构造,但是会启用拷贝构造。 3.如果我们书写了拷贝构造,编译器不会提供任何其他构造。 注:默认的拷贝构造里是成员值的拷贝的,程序员自己书写拷贝构造后,需要自主实现值拷贝。

深拷贝与浅拷贝

浅拷贝:简单的赋值,编译器默认的拷贝构造函数用的是浅拷贝。 深拷贝:在堆区重新申请空间 ,需要自定义拷贝构造函数。

指针变量的值传递,传递的是它们指向空间的地址,当其中一个空间被释放后就无法再使用该空间了(一个空间只能被释放一次)

初始化列表

语法: 构造函数( ):属性1(值1),属性2(值2){构造函数体}

class person { private: int age; int weight; public: //无参情况,age=18,weight=180 person():age(18),weight(180) { cout << "无参构造函数"<<endl; } //有参情况, m_age,weight = m_weight; person(int m_age,int m_weight):age(m_age),weight(m_weight) { cout << "有参构造函数"<<endl; } };

类对象作为类成员

c++中一个类的成员可以是另一个类的对象

class A {};//类A必须书写在类B的前面 class B { A a1; A a2; };

如上例,当创建类B对应的对象时,需要先创建两个A类对象。 先构造其他类的对象,当构造完所有的后,再构造自身

静态成员

静态成员变量

class A { privateint a; static int b;//特点1,b是属于整个类的,而不是属于某一个对象,使用时采用类名::变量名 }; int A::b = 10;//特点2,在类内声明,但需要在类外进行初始化 数据类型 类名::变量名 = 数值; //特点3,在编译阶段就会分配内存

静态成员函数

所有对象共享的一个函数。2. 只能访问静态成员变量。 class A { public: static void fun() { //仍然有访问权限限制 } }; //可以通过对象访问,只能访问静态变量。 A p; p.fun(); //也可以通过类名访问,只能访问静态变量。 A::fun();

c++对象模型和this指针

变量和成员函数分开存储

在c++中,类的成员变量和函数分开存储, 只有非静态成员变量才属于类的对象。 空类占用内存大小为一个字节

this指针

this指针指向被调用成员函数所属的对象,不需要定义,可以直接使用 用途: 1.当形参和员变量同名时,可以使用this指针区分 2.在类的非静态成员函数中返回对象本身,可以使用return *this。

class A { private: int age; public: A(int age)//这里形参和变量同名 { //age = age;这样写实际是形参给形参赋值 this->age = age;//this指针指向p1的age } A set(int x) { age = age + x; return *this; } }; A p1(18);//给p1的age赋值18 p1.set(18);//给p1的age加18

空指针访问成员函数

class person { int m_age; public: void show() { cout << m_age << endl; //这里 m_age 相当于this->m_age //空指针没有指向一个确切的值,错误代码 } }; int main() { person *p = NULL; p->show(); }

const修饰成员函数

常函数: 1.成员函数后加const就变成了常函数。 2.常函数不可以修改成员属性。 3.成员属性声明时加mutable后,在常函数中依然可以修改。 常对象: 1.声明对象前加const就变成了常对象。 2.常对象只能调用常函数。

class person { public: //this指针的本质是指针常量,其指向不可以被修改(person * const this) void showperson()const//相当于const person * const this { this->name = 10;//错误代码 this->name1= 10;//正确代码 int name; mutable int name1; void func() { } }; int main() { const person p;//常对象 p.name = 100;//错误代码 p.name1 = 100//正确代码 p.func()//错误代码,常对象不能调用普通函数,因为普通函数有可能修改属性 }

友元

友元的目的就是让一个函数或者类能访问另一个类中的私有成员 关键字:friend

全局函数做友元

class A { public: int a; friend void test1()//友元函数的声明 private: int b; }; void test() { A a1; cout << a1.b <<endl;//无法访问,b是私有的 } void test1() { A a2; cout << a2.b <<endl;//可以访问,因为test1是类A的友元 }

友元函数仅仅只是类A的朋友,不属于类A的成员,不受权限影响

类做友元

class A { private: friend class B;//声明B是A的友元类 static int a; void test()//类A的私有函数test { cout << "hello" << endl; } }; int A::a = 10; class B { public: A *p;//定义一个A类的指针 void fun() { p->test();//调用类A的私有函数 cout << p->a << endl;//打印a的值 } }; int main() { B b1; b1.fun();//调用b1的fun函数 }

成员函数做友元

class B//这里写在最前面的原因是要让A知道B是谁 { public: void fun(); }; class A { private: static int a; friend void B::fun(); }; int A::a = 10; void B::fun() { A *p; cout << p->a << endl; } int main() { B b1; b1.fun(); }

运算符重载

概念:给已有的运算符重新进行定义,赋予其另一种功能

加号运算符重载

继承

graph LR 交通工具-->汽车 交通工具-->火车 交通工具-->轮船

汽车、火车、轮船除了拥有交通工具的共性之外,还拥有自己的特性。 通过继承,我们可以减少代码的书写量,继承的过程,实际上也就是封装范围逐步扩充的过程。

继承的基本语法

语法:class 子类名:继承方式1 父类名1,继承方式2 父类名2 ……

class A//声明父类A { }; class B:public A//B以公有的方式继承A { //成员1,从基类继承而来的成员,体现其共性 //成员2,属于自己的成员,体现了其个性 }

子类也叫派生类,父类也叫基类。 派生和继承其实说的是一件事情,只不过它们的发起和承受方相反。

继承方式

继承方式有三种:public,private,protected。 规则: 1.以公有方式继承时,公有权限和保护权限不变,私有权限不可访问 2.以保护方式继承时,公有权限和保护权限都变为保护,私有权限不可访问。 3.以私有方式继承时,公有权限和保护权限都变为私有,私有权限不可访问。

继承中的对象模型

class father { private: int a; protected: int b; public: int c; }; class son:public father { int d; }; sizeof(son) 的结果是 16.

无论变量访问权限是什么,子类会将父类的所有非静态变量继承

继承中的构造和析构顺序

子类继承父类后,当创建子类对象后,也会调用父类的构造函数,当创建一个子类后,构造它的父类,再构造它自己。

class base { base() { cout <<"base 构造"<<endl; } ~base() { cout <<"base 析构"<<endl; } }; calss son:public base { son() { cout <<"son 构造"<<endl; } ~son() { cout <<"son 析构"<<endl; } }; son s1的运行结果: base 构造 son 构造 son 析构 base 析构

继承同名成员的处理方式(重定义)

访问子类同名成员,直接访问即可访问父类同名成员,需要加上作用域(注明是谁的) class A { public: int a = 100; }; class B:public A { public: int a = 200; //cout << a << endl; 值为200 //cout << A:a << endl; 值为100 };

当子类中出现父类的同名函数时,子类会隐藏所有父类的同名函数(即使参数不一致)

菱形继承(钻石继承)

graph LR A-->B A-->C B-->D C-->D

继承关系如图,B和C继承自A,而D又继承自B和C,这样的继承就叫做菱形继承。

当B和C中有同名数据时,在D中使用时会有二义性,需要加作用域区分当D继承A的独有数据时,由于分两条线继承,也会报二义性。

虚继承: class A : virtual public B — A 称为为虚基类(这样继承的数据就只有一份)

多态

多态的基本概念

多态分为两类: 1.静态多态:函数重载和运算符重载(早绑定,编译阶段确定地址) 2.动态多态:派生类(有父子关系)和虚函数实现(晚绑定,运行阶段确定地址)

class A { public: void speak()//前面加virtual可实现地址晚绑定,打印B speak { cout << "A speak" <<endl; } }; class B:public A { public: //返回值类型 函数名 参数列表完全相同才能实现重写 void speak()//重写父类的虚函数 { cout << "B speak" <<endl; } }; //早绑定,在编译时就确定了是A的地址 void dospeak(A &pa)//A &pa = b1; 父类的指针或引用可指向子类对象 { pa.speak();//结果为A speak(非虚看指针,虚看对象) } B b1; dospeak(b1);

总结: 多态的满足条件:1.有继承关系. 2.子类重写父类的虚函数

多态的深入剖析

在函数名前加virtual实际上是添加了一个vfptr指针 该指针指向这个函数的地址,保存在一个虚函数表里 在子类重写虚函数会覆盖掉这个地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wo4sxzY3-1603264360265)(https://note.youdao.com/yws/api/personal/file/E706CD44DFB046E3BAE3FB5C712C1400?method=download&shareKey=7dfb0e42f1c0431fbec8f9dbd7c3d230)]

纯虚函数和抽象类

多态中,通常父类中虚函数的实现是毫无意义的,因此可以将虚函数写为纯虚函数 拥有纯虚函数的类称为抽象类

virtual 返回值类型 函数名 参数列表(参数列表)=0;//父类约定访问规则,子类具体实现

特点: ①无法实例化对象 ②子类必须重写父类中的纯虚函数,否则也属于抽象类

虚析构和纯虚析构

当以父类指针访问子类时,在释放父类指针时不会调用析构函数 虚析构:

class A { public: A() { cout << "A 构造" << endl; } ~A()//想要调用B的析构需要加virtual { cout << "A 析构" << endl; } virtual void fun() = 0; }; class B:public A { public: B() { cout << "B 构造" << endl; } ~B() { cout << "B 析构" << endl; } void fun() { cout << "fun" << endl; } }; int main() { A* a = new B(); a->fun(); delete a;//释放时只会调用A的析构函数 }
最新回复(0)