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