CPP_类

指针和引用的区别

1、引用是给变量起别名,内部实现是指针常量(int* const ref = &a),其可以简单的理解为本体指针存放的是变量地址
2、引用的本质是指针常量,指向不可修改,而指针可以修改
3、引用在被创建时必须初始化,而指针可与不初始化;
4、引用不能为空,指针可以指向NULL;
5、引用变量ref保存的是a的地址;sizeof(引用)=指向变量的大小;sizeof(指针)=指针本身的大小
6、引用使用时无需解引用,指针使用时需要解引用(即*p即是解引用)
7、关于自增自减:指针加减是地址的跨越,引用加减是内容的加减

关于public、protected、private在继承中的理解

从低到高分为三级:public protected private
如果子类继承了父类,则对于某一个属性成员(或者函数成员),需要检查其定义(如:public int a)以及检查子类的属性(如:protected son:public father),然后其中成员的属性将取其中的较高级者(如:刚刚举例中的a现在在子类中应该为protected)。

且:public可被继承可被外界访问 protected可被继承不可被外界访问 private不可被继承不可被外界访问

单例模式

#include <iostream>

using namespace std;


class Printer {

private:
	//1、定义一个静态的 对象指针变量 保存唯一示例地址
	static Printer* singnlePrint;
public:
	int count = 0;

public:
	//2、定义一个方法,获取单例指针
	static Printer* getSignlePrint(void) {
		return singnlePrint;
	}
	//4、设置功能函数
	void printText(const char* str) {
		cout << "打印" << str << endl;
		count++;
	}
private:
	//3、防止实例化其它对象 将函数构造私有化
	Printer() {}
	Printer(const Printer& ob) {}
};
Printer* Printer::singnlePrint = new Printer;


int main(int argc, char* argv[]) {
	Printer* p1 = Printer::getSignlePrint();
	p1->printText("a");
	p1->printText("b");

	Printer* p2 = Printer::getSignlePrint();
	p2->printText("c");
	p2->printText("d");

	cout << p2->count << endl;
}

返回对象本身(*this)

#include <iostream>

using namespace std;


class myCout {
public:

	myCout& Cout(const char* str) {
		cout << str << " ";
		return *this;//返回当前调用该函数的对象(见myCout test;下一行的解释)
	}
};


int main(int argc, char* argv[]) {
	myCout test;
	//执行return *this以后,test.Cout("aaa")相当于又变成了test,因此可以继续调用函数
	test.Cout("aaa").Cout("bbb").Cout("ccc");
}

内联函数

在这里插入图片描述

虚基类

在这里插入图片描述
(class Sheep:virtual public Animal)
Animal是虚基类,实际大小为4size,而Sheep是他的子类,但是却有8size大小,多出来的4size用来存放了虚基类指针。

而虚基类指针作用就是指向虚基类表,虚基类表就是下面的Sheep::$vbtable, 保存了当前指针相对于虚基类首地址的偏移量

总之,之所以需要虚基类指针和虚基类表,目的就是保证不管多少个继承,虚基类的数据也只有一份:原先如果触发菱形继承(父类中的数据会在一代子类一中和一代子类二中继承,而二代子类如果多继承于一代子类一和一代子类二,就相当于继承了两份父类的数据),而现在如果触发菱形继承,将只会继承一代子类一和一代子类二的指针,系统只要对这指针稍加修改偏移量,就可以指向虚基类表,数据也就不会重复, 也就消除了二义性,但是用了更多的空间

空间使用对比:
在这里插入图片描述

如以下的SheepTuo就是多继承于Sheep和Tuo:
在这里插入图片描述
虚基类表,记录着Sheep到Animal偏移量为8,Tuo到Animal偏移量为4
在这里插入图片描述

多态

静态多态和动态多态区别:函数地址是在编译时候确定还是在运行时候确定

虚函数(实现动态多态)

注意:如果Animal没有涉及到继承,virtual定义的函数指针变量,就指向自身的函数;如果涉及到继承,那么子类将先继承父类的指针,然后编译器将该虚函数指针指向自身的函数,即将表的内容更改,见图:
在这里插入图片描述

class Animal {
public:
	void sleep() {
		cout << "Animal 在睡觉" << endl;
	}

	//此处为虚函数
	virtual void eat() {
		cout << "Animal在吃饭" << endl;
	}
};

class Cat :public Animal {
public:
	void sleep() {
		cout << "Cat 在睡觉" << endl;
	}

	virtual void eat() {
		cout << "Cat在吃饭" << endl;
	}
};

void test0() {
	//用基类(指针或者引用)保存了子类对象(的地址或者别名)(向上转换)
	//p保存了Cat的起始地址,此时不会溢出,是指向了Cat中Animal的内容是安全的
	//但是反过来Cat* p = new Animal;就不安全,会导致溢出,访问非法空间
	Animal* p = new Cat;
	//此时访问的是动物在睡觉
	//但是由于是基类,所以是无法访问Cat的sleep函数的
	p->sleep();
}

void test1() {
	Animal* p = new Cat;
	//输出的是猫在吃饭,因为这是个虚函数
	p->eat();
}

在这里插入图片描述
此处是虚函数指针指向的虚函数表:
在这里插入图片描述

可扩展性

以后如果需要修改某些执行的功能,只需要比如自己添加个Son5,然后将自己的操作写入Son5的sleep函数中,后面就可直接调用,不需要修改Base类和其他子类中的内容:

class Base {
public:
	virtual void sleep() {
		cout << "父亲在睡觉" << endl;
	}
};

class Son1 :public Base {
public:
	virtual void sleep() {
		cout << "安静" << endl;
	}
};

class Son2 :public Base {
public:
	virtual void sleep() {
		cout << "小声" << endl;
	}
};

class Son3 :public Base {
public:
	virtual void sleep() {
		cout << "吵闹" << endl;
	}
};

class Son4 :public Base {
public:
	virtual void sleep() {
		cout << "大叫" << endl;
	}
};

void sleepFun(Base &ob) {
	ob.sleep();
}


int main(int argc, char* argv[]) {
	Son1 son1;
	Son2 son2;
	Son3 son3;
	Son4 son4;
	sleepFun(son1);
	sleepFun(son2);
	sleepFun(son3);
	sleepFun(son4);
	return 0;
}

纯虚函数/抽象函数

即完全不需要调用基类的某个虚函数,就可以将这个基类中的函数设置为virtual void sleep() = 0;
抽象函数:如果一个类中有纯虚函数,那么这个类就是抽象类, 抽象类不能实例化对象
抽象类用法:希望某个基类仅仅作为一个派生类的接口,而不希望用户创建一个基类对象。
注意 :当继承一个抽象类时,派生类 必须 实现该抽象类中的所有纯虚函数,否则该子类仍然是个抽象类

虚析构

执行Animal* p = new Cat后,会先调用父类构造,然后子类构造,但是在最终,不会调用子类的析构,只能释放父类的析构。
原因分析:因为p指针实质上还是Animal类型的,结束后还是会回去寻找Animal的析构函数。但是这样就会导致子类的内存泄漏,因此需要使用虚析构。
解决方法:在父、子类虚析构前加virtual即可。

虚析构作用:通过基类指针、引用释放子类所有空间

泛型编程(模板)

注意:一般函数模板用typename,类模板用class

函数模板

template<typename T>
void mySwap(T& a, T& b) {
	T tmp = a;
	a = b;
	b = tmp;
}

void test0() {
	int a = 10, b = 20;
	mySwap(a, b);
	cout << a << "    " << b << endl;
}

void test1() {
	char a = 'a', b = 'b';
	//也可以用mySwap<char>(a, b);
	mySwap(a, b);
	cout << a << "  " << b << endl;
}

int main(int argc, char* argv[]) {
	test0();
	test1();
}

遇到无法套用函数模板的情况

解决方法一
提供函数模板,具体化

class Person {
public:
	int a;
	int b;

public:
	Person() {
		a = 10;
		b = 20;
	}
}

template<typename T>
T& myMax(T& a, T& b) {
	return a > b ? a : b;
}

template<>
Person& myMax(Person& ob1, Person& ob2) {
	return ob1 > ob2 ? ob1.a : ob2.a;
}

解决方法二
重载运算符(推荐),因为这样可以不用改动模板

class Person {
public:
	int a;
	int b;

public:
	Person() {
		a = 10;
		b = 20;
	}

	bool opration > (const Person& ob) {
		return (this->a > ob.a);
	}
};

template<typename T>
T& myMax(T& a, T& b) {
	return a > b ? a : b;
}

template<>
Person& myMax(Person& ob1, Person& ob2) {
	return ob1 > ob2 ? ob1.a : ob2.a;
}

类模板

template<class T1,class T2>
class Data {
private:
	T1 name;
	T2 num;
public:
	Data(T1 name, T2 num) {
		cout << "有参构造" << endl;
		this->name = name;
		this->num = num;
	}

	void showData() {
		cout << this->name << "  " << this->num << endl;
	}

	~Data() {
		cout << "析构函数" << endl;
	}
};

void test0() {
	//类模板不能进行类型自动推导
	//需要手动添加
	Data<string,int> data1("hello", 100);
	data1.showData();
	Data<int, string> data2(200, "good");
	data2.showData();
}

容器特点

在这里插入图片描述

问题

重写、重载、重定义

重载:参数顺序,数量,类型不同,但是类中方法名和方法返回值类型相同
重定义:
在这里插入图片描述
重写:
在这里插入图片描述

CPP的特点

1、面向对象
封装:CPP将相关的函数功能封装到一起,加以权限区分,需要用到的时候可以根据自身的权限直接调用或者访问数据
继承:提高开发效率避免重复开发
多态:函数、运算符重载(静态多态);虚函数(动态多态)
2、泛型编程(模板)

CPP的动态捆绑机制?(CPP有何特性)

当编译器发现类中有虚函数,那么编译器会创建一个虚函数表(保存虚函数的入口地址)以及一个虚函数指针(用来指向虚函数表),多态调用时,会根据这个指针找到虚函数表的位置,绑定函数地址,虚函数地址会根据继承的对象动态变化

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值