C++类简要概括

一、类和对象

1.1 封装

封装是C++面向对象三大特征之一。
将属性和行为作为一个整体;设计类的时候,属性和行为写在一起。

访问权限有三种:
public:类内可以访问,类外可以访问
private:类内可以访问,类外不可以访问,子类不可以访问父类私有内容
protected:类内可以访问,类外不可以访问,子类可以访问父类保护内容

struct默认权限是公有,class默认权限是私有。

1.2对象初始化和清理

构造函数: 主要作用在于创建对象时为对象的成员属性赋值,构造函数又编译器自动调用,无需手动调用

  • 函数名称与类名相同
  • 没有返回值也不写void
  • 有参数,可以重载
  • 自动调用,只会调用一次

析构函数: 主要作用在于对象销毁前系统自动调用,执行一些清理工作

  • 没有返回值,不写void
  • 函数名称为类名前加‘~’
  • 没有参数,所以不能不能重载
  • 自动调用,只调用一次

1.3 构造函数的分类

使用:
–无参构造(默认构造)
–有参构造

–普通构造
–拷贝构造函数:参数是类,复制一个类

Person(const Person&p){
   
}

调用:
–括号法

Person p1;  //默认构造函数不要加()
//Person p1(); 编译器会认为是一个函数的声明,不会认为是在创建对象
Person p2(10);
Person p3(p2); //拷贝构造函数

–显示法

Person p1;
Person p2 = Person(10);
Person p3 = Person(p2);
Person(10);//匿名对象:当前行执行结束后,系统会立即回收掉匿名对象
//不要利用拷贝构造函数 初始化匿名对象;
//编译器会认为Person (p3) ==== Person p3;造成对象重定义

–隐式转换法

Person p1 = 10;
Person p2 = p1;

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

  • 使用一个已经创建完毕的对象初始化一个新对象
  • 值传递的方式给函数参数传值
  • 以值方式返回局部对象

在这里插入图片描述

深拷贝和浅拷贝:
利用编译器提供的拷贝构造函数们会提供浅拷贝操作,但会带来堆区内存重复释放的问题,需要利用深拷贝进行解决
简单来说就是,如果类中成员有指针,在调用默认拷贝构造函数时候,只会复制地址,造成新对象指针指向的是同一片内存地址,当两个对象最后调用析构函数时,先后释放同一片地址,那么后释放的将会有问题!因此需要深拷贝,手动写拷贝构造函数,其中重新申请一片空间,存放需要拷贝的值。这样两次释放则不会重复

类对象作为类成员: 此时两个类构造函数和析构函数先后问题
比如A类中有一个成员是类B
结论:
构造—先B后A
析构—先A后B

1.4 静态成员

在成员前加上static,称为静态成员
静态成员变量:

  • 所有对象共享同一份数据
  • 在编译阶段分配内存(程序运行前,全局区)
  • 类内声明,类外初始化

最后一点做个说明:静态成员变量在类内声明,需要在类外定义;所以访问也有两种方法:通过对象访问,或者直接通过类名访问。但要注意,这里两种访问是针对公有静态成员变量的,如果是私有静态成员变量,则不能通过类外访问。

class Person{
   
	static int m;
};
int Person::m=100;

静态成员函数:

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

1.5成员对象模型和this指针

空对象占用内存空间为1字节,为了区分空对象占内存的位置,每个空对象有一个独一无二的字节。
在这里插入图片描述

this指针指向被调用的成员函数所属的对象
this指针隐含每一个非静态成员函数内的一种指针

空指针调用成员函数

1.6const修饰成员函数

常函数: 成员函数后加const;
常函数内不可以修改成员属性。
解释:本质修饰的是this指针,让this指针指向的值也不可以修改。this指针本质是一个指针常量,即指针的指向不可以修改。
成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象: 声明对象前加const
常对象只能调用常函数

1.7 友元

函数或者类访问一个类中的私有成员:friend
三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

1.8 运算符重载

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


//1.成员函数重载
Person operator+(Person& n){
   
	Person temp;
	temp->m = this->m + n->m;
	return temp;
}

Person m1;
Person m2;
Person m3 = m1.opterator+(m2);//成员函数运算符重载的本质
Person m3 = m1 + m2;  //代码表现

//2.全局函数重载运算符
Person operator+(Person& n1, Person&n2){
   
	Person temp;
	temp->m = n1->m + n2->m;
	return temp;
}
Person m1;
Person m2;
Person m3 = operator+(m1, m2);//全局函数运算符重载的本质
Person m3 = m1 + m2;  //代码表现

1、对于内置运算符不能进行重载
2、不能滥用

对于输出运算符重载“<<”,通常不用成员函数重载,成员函数实现的重载功能要求类在左边,而我们想要的功能是类在右边,cout在左边,因此只能利用全局函数重载左移运算符

void operator<<(ostream& cout, Person&p){
   
	cout<<"m_a = "<<p->m_a;
}

cout<<p;

但此时并不完整,我们经常需要在cout输出后面加换行也就是要cout<<p<<endl;但上述实现方式只是cout<<p;就已经结束了,不能达到连续cout的目的,如果要连续cout,就要保证前部分cout<<p的结果也是一个cout,因此可以将重载函数更新为:

ostream& oterator<<(ostream& cout, Person&p){
   
	cout<<"m_a = "<<p->m_a;
	return cout;
}//此时就可以链式输入了

但是,通常对于类的成员变量,我们定义为私有变量更为安全,但全局函数又不能访问类的私有变量,因此需要将我们定义的全局左移运算符重载函数定位为类的友元函数!此时就可以访问类的私有变量了。

现在重载递增运算符“++”,分为前置递增和后置递增
首先是前置递增,

class Myinter{
   
public:
	Myinter(){
   m=1;}
private:
	int m;
};

//已经实现左移运算符的输出
//用一个类表示整数,自己实现该类的递增
Myinter& Myinter::operator++(){
   //成员函数实现递增运算符重载
	m++;
	return *this;
}

这里要注意一个问题,为什么返回引用,如果返回类,递增结果也能实现,但是有错误,整数的递增是可以迭代实现的,比如++(++M),实现两次++,如果我们实现的递增运算符返回类本身,当出现迭代计算,虽然结果正确,但此时输出的已经是另一个类了,原本类值没变,也就是说接下来在使用该类时,这个类的实现没有得到++的效果。
现在是后置递增运算符

Myinter Myinter::operator++(int){
   //成员函数实现后置递增运算符重载
	Myinter temp= *this;
	m++;
	return temp;
}

两点注意

  1. 前置与后置的重载实现,函数名称是相同的,参数也是相同的,因此造成了函数重定义,所以,对于后置递增运算符的重载,在参数位置写一个int,int代表占位参数,用于区分前置和后置递增
  2. 前置递增中说到,一定要返回引用,因为我们返回的还是本身!而后置递增的实现是,先复制一个temp,然后将自身++,再返回temp,所以后置通过返回一个旧值得副本,本身已经++来实现的,所以后置不能返回引用,一定要返回类

在默认拷贝构造函数时说到深浅拷贝得问题,如果类中有变量定义在堆区,浅拷贝则会报错。而赋值运算符同样也有该问题,虽然编译器给类提供了赋值运算符得重载函数,使得类可以进行值拷贝,但如果涉及堆区得数据,通常也会报错,因此在特殊场合需要自己实现赋值运算符得重载。

//同样类中有一个整型数,但不同的是,这次该数存在堆区,因此用指针实现
class Person{
   
public:
	Person(int a){
   
		age = new int(a);
	}
	~Person(){
   
		if(age!=NULL){
   
			delete age;
			age=NULL;
		}
	}
private:
	int *age;
}

//测试
Person p1(10);
Person p2(20);

p2=p1;
cout<<p1;
cout<<p2;

如果逻辑按上述代码运行,则程序会出现崩溃
因为,我们使用了默认赋值运算符得重载函数,实现了类得整体属性赋值,但是类中涉及到堆区数据,析构函数就要有相应的清空操作。第一个类对象存了一个地址,我们通过默认赋值重载函数赋值给了第二个类对象,也就是第二个类此时也指向了堆区某一地址,与对象1相同。当程序执行完,要对类对象进行回收,也就是清空两个类指向的堆区数据时,第一个先清了,正常,但是,第二个类中也存了该地址,当第二个类想去清空这段地址得数据时,发现是空的!刚才已经清空过了,所以直接报错了,也就是造成了堆区内存重复释放程序崩溃,也就是通常说的浅拷贝,所以需要深拷贝来实现。

因此当类中出现堆区数据时,需要写一个赋值运算符得重载函数,大体逻辑为:首先要判断本身指针是否为空,因为赋值运算符使用前,变量本身是可以已经有值的,也就是说如果类中指针已经指向了一片堆区数据,我们要先将该内存释放,使得指针先指向空,然后利用传得类中指针内存得数据得值,新开辟一个新地址,也就是new,然后赋值给我们类本身得指针,此时类指针指向得地址与参数类中指针地址不同,但值相同,所以达到了赋值效果,并且delete时,两个地址不同,不会出现重复清空的情况,实现了深拷贝。
还有一点要注意,赋值运算符是可以连续使用的,也就是a=b=c,因此重载的赋值运算符要返回类引用(本身)。

对于关系运算符,实现是比较简单的,就不多说了

函数调用运算符() 重载,而重载后的使用的方式非常像函数的调用,因此称为仿函数,仿函数没有固定写法,非常灵活。
例如:

int Myadd::operator()(int a, int b){
    //Myadd是对应的类
	return a+b;
}

//测试
Myadd myadd;
int ret = myadd(100,100);
cout<<ret<<endl;

上述利用仿函数实现了加法,看似平平无奇,实则也很普通,就是格式似乎跟以往不一样,以前是用一个函数实现我想要的功能,比如加入,这里,我用一个类的仿函数实现+。这只是一种使用方法,可以根据代码需求写各种各种的仿函数,所以说仿函数的使用很灵活

cout<<Myadd()(100,100)<<endl;

在介绍一个匿名函数对象,我们说过匿名对象,一行代码使用匿名对象,使用完后直接清除,不等程序整个执行完,而上面是匿名函数对象,仿函数+匿名对象。仔细看看,悟!

1.9 继承

继承的好处:减少重复的代码
在这里插入图片描述
对于三种继承方式,无论哪种继承方式,父类得私有成员都不可访问;而公有继承得成员权限不变,保护继承后的父类中的公有跟保护成员都变成子类得保护成员;私有继承后的父类中的公有跟保护成员都变成子类得私有成员;
但是,父类中的私有成员,虽然没有访问权限,但是是被继承到子类中的,只是被编译器隐藏了

父类和子类中构造和析构顺序???
---->父类构造函数
---->子类构造函数
---->子类析构函数
---->父类析构函数

当父类跟子类中出现同名得成员,怎么访问呢???
—>默认访问得是子类得该成员,如果要访问父类得,需要加上父类作用域,也就是要指名道姓表示我要访问的是父类中的该成员

继承同名静态成员

多继承语法???
C++允许多继承父类,但是存在隐患,也就是一个成员,在多个父类中同时存在,此时子类访问该成员,会出现歧义,也就是不知道访问得是哪个父类中的该成员,因此,对于该情况,需要添加作用域以区分父类。

菱形继承???
两个派生类继承同一个类—又有某个类同时继承两个派生类。
在这里插入图片描述

1.10 多态

在这里插入图片描述
在这里插入图片描述

#include<iostream>
using namespace std;
class Animal{
   
        public:
                virtual void Speak(){
   cout<<"animal speak!"<<endl;};

};

class Cat: public Animal{
   
        public:
                void Speak(){
   cout<<"cat speak!"<<endl;};
};

class Dog:public Animal{
   
        public:
                void Speak(){
   cout<<"dog speak!"<<endl;};
};
void DoSpeak(Animal &ani){
   
        ani.Speak();
}

int main(){
   
        Cat c;
        Dog d;
        DoSpeak(c);
        DoSpeak(d);
        return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

相关推荐

  1. C++ 字符串 简易封装

    2024-02-22 21:26:02       26 阅读
  2. c++ 简单的日志 CCLog

    2024-02-22 21:26:02       7 阅读
  3. 二叉搜索树的简单C++实现

    2024-02-22 21:26:02       37 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-22 21:26:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-22 21:26:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-22 21:26:02       19 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-22 21:26:02       20 阅读

热门阅读

  1. 头歌C++语言之选择排序练习题

    2024-02-22 21:26:02       30 阅读
  2. 215数组中的第K个最大元素

    2024-02-22 21:26:02       31 阅读
  3. µC/OS-II---日常学习

    2024-02-22 21:26:02       27 阅读
  4. redis最佳实践

    2024-02-22 21:26:02       29 阅读
  5. 79.SpringBoot的核心注解

    2024-02-22 21:26:02       31 阅读
  6. ORACLE之 decode函数

    2024-02-22 21:26:02       31 阅读
  7. xml里面<foreach>标签用法

    2024-02-22 21:26:02       27 阅读
  8. K8S的apiVersion含义

    2024-02-22 21:26:02       24 阅读