建设网站费怎么入账,网址导航app下载,网站没有经过我司审核通过白名单,建设跳转公积金网站C面向对象知识
内存字节对齐 #pragma pack(n) 表示的是设置n字节对齐,windows默认是8字节#xff0c;linux是4字节#xff0c;鲲鹏是4字节 struct A{char a;int b;short c;
};char占一个字节#xff0c;起始偏移为零#xff0c;int占四个字节#xff0c;min(8,4)4#x…C面向对象知识
内存字节对齐 #pragma pack(n) 表示的是设置n字节对齐,windows默认是8字节linux是4字节鲲鹏是4字节 struct A{char a;int b;short c;
};char占一个字节起始偏移为零int占四个字节min(8,4)4所以应该偏移量为4所以应该在char后面加上三个字节不存放任何东西short占两个字节min(8,2)2;所以偏移量是2的倍数而short偏移量是8是2的倍数所以无需添加任何字节所以第一个规则对齐之后内存状态为0xxx|0000|00此时一共占了10个字节但是还有结构体本身的对齐min(8,4)4所以总体应该是4的倍数所以还需要添加两个字节在最后面所以内存存储状态变为了 0xxx|0000|00xx一共占据了12个字节 内存对齐规则 对于结构的各个成员第一个成员位于偏移为0的位置以后的每个数据成员的偏移量必须是 min( #pragma pack()指定的数, 这个数据成员的自身长度 )的倍数在所有的数据成员完成各自对齐之后结构或联合体本身也要进行对齐对齐将按照 #pragam pack指定的数值和结构或者联合体最大数据成员长度中比较小的那个也就是 min(#pragram pack() , 长度最长的数据成员) 需要对齐的原因 平台原因移植原因不是所有的硬件平台都能访问任意地址上的任意数据某些硬件平台只能在某些地址处取某些特定类型的数据否则抛出硬件异常硬件原因经过内存对齐之后CPU的内存访问速度大大提升。访问未对齐的内存处理器要访问两次数据先读高位再读低位访问对齐的内存处理器只要访问一次为了提高处理器读取数据的效率我们使用内存对齐
面向对象三大特性
通过类创建一个对象的过程叫实例化实例化后使用对象可以调用类成员函数和成员变量其中类成员函数称为行为类成员变量称为属性。类和对象的关系类是对象的抽象对象是类的实例
封装 把客观事物封装成抽象的类并且类可以把自己的数据和方法只让可信的类或者对象操作对不可信的进行信息隐藏。publicprivateprotected 继承 基类父类—— 派生类子类 多态
双冒号、using和namespace namespace主要用来解决命名冲突的问题 必须在全局作用域下声明命名空间下可以放函数变量、结构体和类命名空间可以嵌套命名空间命名空间是开放的可以随时加入新成员添加时只需要再次声明namespace然后添加新成员即可 双冒号::作用域运算符 全局作用域符::name用于类型名称类、类成员、成员函数、变量等前表示作用域为全局命名空间类作用域符class::name用于表示指定类型的作用域范围是具体某个类的命名空间作用域符namespace::name:用于表示指定类型的作用域范围是具体某个命名空间的 using分为using声明和using编译指令 using std::cout; //声明using namespace std; //编译指令尽量使用声明而不是编译指令不同命名空间中可能会有相同的变量名编译指令执行两个命名空间后会产生二义性
内联函数和函数重载 内联函数 相当于把内联函数里面的内容写在调用内联函数处相当于不用执行进入函数的步骤直接执行函数体相当于宏却比宏多了类型检查真正具有函数特性不能包含循环、递归、switch 等复杂操作在类声明中定义的函数除了虚函数的其他函数都会自动隐式地当成内联函数内联函数对于编译器而言只是一个建议编译器不一定会接受这种建议即使没有声明内联函数编译器可能也会内联一些小的简单的函数。 C的函数名称可以重复称为函数重载。 其中必须在同一作用域下的函数名称相同不能是一个在全局一个局部或者不同的代码块中可以根据函数参数的个数、类型const也可以作为重载条件、顺序不同进行函数重载但不能用函数返回值进行重载当函数重载遇到函数默认参数时要注意二义性。
虚函数可以是内联函数吗
虚函数可以是内联函数内联是可以修饰虚函数的但是当虚函数表现多态性的时候不能内联。内联是在编译期内联而虚函数的多态性在运行期编译器无法知道运行期调用哪个代码因此虚函数表现为多态性时运行期不可以内联。inline virtual 唯一可以内联的时候是编译器知道所调用的对象是哪个类如 Base::who()这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
构造函数/析构函数
构造函数和析构函数分别对应变量的初始化和清理变量没有初始化使用后果未知没有清理则会内存管理出现安全问题。 当对象结束其生命周期如对象所在的函数已调用完毕时系统会自动执行析构函数 构造函数与类名相同没有返回值不写void可以发生重载可以有参数编译器自动调用只调用一次。析构函数~类名没有返回值不写void不可以发生重载不可以有参数编译器自动调用只调用一次。 构造函数 系统会默认给一个类提供三个函数默认构造函数无参函数体为空、默认拷贝构造和析构函数无参函数体为空其中默认拷贝构造可以实现简单的值拷贝。提供了有参构造函数就不提供默认构造函数提供了拷贝构造函数就不会提供其他构造函数若自己定义可有参构造也需要自定义无参构造函数 析构函数 如果一个类中有指针且在使用的过程中动态的申请了内存那么最好自定义析构函数在销毁类之前释放掉申请的内存空间避免内存泄漏
拷贝构造函数与深浅拷贝
拷贝构造函数的参数必须加const因为防止修改本来就是用现有的对象初始化新的对象。 拷贝构造函数的使用时机 使用已经创建好的对象初始化新对象 A a; A b a; A c(a); b c;//b c不是初始化调用赋值运算符 以值传递的方式来给函数参数传值以值方式返回局部对象不常用一般不返回局部对象 深拷贝和浅拷贝 只有当对象的成员属性在堆区开辟空间内存时才会涉及深浅拷贝如果仅仅是在栈区开辟内存则默认的拷贝构造函数和析构函数就可以满足要求。 浅拷贝使用默认拷贝构造函数拷贝过程中是按字节复制的对于指针型成员变量只复制指针本身而不复制指针所指向的目标因此涉及堆区开辟内存时会将两个成员属性指向相同的内存空间从而在释放时导致内存空间被多次释放使得程序down掉。浅拷贝的问题当出现类的等号赋值时系统会调用默认的拷贝函数——即浅拷贝它能够完成成员的一一复制。当数据成员中没有指针时浅拷贝是可行的。但当数据成员中有指针时如果采用简单的浅拷贝则两类中的两个指针将指向同一个地址当对象快结束时会调用两次free函数指向的内存空间已经被释放掉再次free会报错另外一片空间被两个不同的子对象共享了只要其中的一个子对象改变了其中的值那另一个对象的值也跟着改变了所以这时必须采用深拷贝深拷贝自定义拷贝构造函数在堆内存中另外申请空间来储存数据从而解决指针悬挂的问题。需要注意自定义析构函数中应该释放掉申请的内存
我们在定义类或者结构体这些结构的时候最后都重写拷贝函数避免浅拷贝这类不易发现但后果严重的错误产生
只在堆上/栈上创建对象 只能在堆上生成对象将析构函数设置为私有。 原因C是静态绑定语言编译器管理栈上对象的生命周期编译器在为类对象分配栈空间时会先检查类的析构函数的访问性。若析构函数不可访问则不能在栈上创建对象。只能在栈上生成对象将new 和 delete 重载为私有。 原因在堆上生成对象使用new关键词操作其过程分为两阶段第一阶段使用new在堆上寻找可用内存分配给对象第二阶段调用构造函数生成对象。 将new操作设置为私有那么第一阶段就无法完成就不能够在堆上生成对象。 this指针 为什么会有this指针 在类实例化对象时只有非静态成员变量属于对象本身剩余的静态成员变量静态函数非静态函数都不属于对象本身因此非静态成员函数只会实例一份多个同类型对象会共用一块代码由于类中每个实例后的对象都有独一无二的地址因此不同的实例对象调用成员函数时函数需要知道是谁在调用它因此引入了this指针。this指针是对象的首地址。 this指针的作用 this指针是隐含在对象成员函数内的一种指针。当一个对象被创建后它的每一个成员函数都会含有一个系统自动生成的隐含指针this。this指针指向被调用的成员函数所属的对象谁调用成员函数this指向谁*this表示对象本身非静态成员函数中才有this静态成员函数内部没有。 this指针实际上是编译器对非静态成员函数做出的操作在定义函数时会往函数里传入class *this这个参数在函数调用时会传入对象的地址。静态成员函数之所以没有this指针是因为静态成员函数先于对象产生并且是所有对象共享的。 this 并不是一个常规变量而是个右值所以不能取得 this 的地址不能 this。对非静态成员函数默认添加了this指针类型为classname *const this this指针使用 当形参与成员变量名相同时用this指针来区分 为实现对象的链式引用在类的非静态成员函数中返回对象本身可以用return *thisthis指向对象 *this表示对象本身。
常函数和常对象 void func() const //常函数此处func为类成员函数 const Person p2; //常对象
常函数修饰的是this指针不允许修改this指针指向的值如果执意要修改常函数可以在成员属性前加mutable。常对象不允许修改属性不可以调用普通成员函数可以调用常函数。
delete this合法吗
合法但有前提
必须保证 this 对象是通过 new不是 new[]、不是 placement new、不是栈上、不是全局、不是其他对象成员分配的必须保证调用 delete this 的成员函数是最后一个调用 this 的成员函数必须保证成员函数的 delete this 后面没有调用 this 了必须保证 delete this 后没有人使用了
为什么空类大小不为0
sizeof(空class) 1为了确保两个不同对象的地址不同。
静态成员变量与静态成员函数
若将成员变量声明为static则为静态成员变量与一般的成员变量不同无论建立多少对象都只有一个静态成员变量的拷贝静态成员变量属于一个类所有对象共享。静态变量在编译阶段就分配了空间对象还没创建时就已经分配了空间放到全局静态区。
静态成员变量 最好是类内声明类外初始化以免类名访问静态成员访问不到无论公有私有静态成员都可以在类外定义但私有成员仍有访问权限非静态成员类外不能初始化静态成员数据是共享的。 静态成员函数 静态成员函数可以直接访问静态成员变量不能直接访问普通成员变量但可以通过参数传递的方式访问普通成员函数可以访问普通成员变量也可以访问静态成员变量静态成员函数没有this指针。非静态数据成员为对象单独维护但静态成员函数为共享函数无法区分是哪个对象因此不能直接访问普通变量成员也没有this指针。
初始化列表的好处和使用条件
初始化列表的使用条件 const类型的数据引用类型的数据 好处 初始化是直接初始化成员赋值是初始化再赋值
能否通过初始化列表初始化静态成员变量
不能静态成员变量最好类内声明类外初始化。静态成员是单独存储的并不是对象的组成部分。如果在类的内部进行定义在建立多个对象时会多次声明和定义该变量的存储位置。在名字空间和作用域相同的情况下会导致重名的问题。
友元全局函数、友元类、友元成员函数
友元主要是为了访问类中的私有成员包括属性和方法会破坏C的封装性尽量不使用
友元全局函数 友元函数声明可以在类中的任何地方一般放在类定义的开始或结尾一个函数可以是多个类的友元函数只需要在各个类中分别声明友元函数在类内声明类外定义定义和使用时不需加作用域和类名与普通函数无异。
class Building
{friend void goodGay(Building * building); //goodGay是Building的友元函数因此goodGay可以访问building的任意成员
public:Building(){m_Sittingroom 客厅;m_Bedroom 卧室;}string m_Sittingroom;
private:string m_Bedroom;
};//和C语言结构体同传参时尽量不要传递值尽量传递指针
void goodGay(Building * building){cout 别人在访问 building-m_Sittingroom endl;cout 别人在访问 building-m_Bedroom endl; //当不是友元函数时不能访问私有成员
}void test01(){Building building; //或者Building *build new Building;这里如果定义指针需要new否则未初始化goodGay(building);
}友元类 友元不可继承友元是单向的类A是类B的友元类但类B不一定是类A的友元不具有传递性类A是类B的友元类类B是类C的友元类但类A不一定是类C的友元类。
class Building{friend class Person; //Person是Building的友元函数,因此Person可以访问Building的任意成员
public:Building(){this-m_Sittingroom 客厅;this-m_Bedroom 卧室;}string m_Sittingroom;
private:string m_Bedroom;};class Person{public:void test(Building *building){cout building-m_Bedroom endl;}
};void test01(){Building *build new Building;//可以在这里写定义也可以将定义写在Person的构造函数中Person p;p.test(build);
}友元成员函数 使类B中的成员函数成为类A的友元函数这样类B的该成员函数就可以访问类A的所有成员当用到友元成员函数时需注意友元声明和友元定义之间的相互依赖在该例子中类Person必须先定义否则类Building就不能将一个Person的函数指定为友元。然而只有在定义了类Person之后才能定义类Person的该成员函数。更一般的讲必须先定义包含成员函数的类才能将成员函数设为友元。另一方面不必预先声明类和非成员函数来将它们设为友元。
//和C语言中结构体的互引用类似需要先声明一个Building类
class Building;class Person{public:void test(Building *building);void test1(Building *building);
};class Building{friend void Person::test1(Building *building); //先将Person类定义类友元成员函数声明后再使用friend
public:Building(){this-m_Sittingroom 客厅;this-m_Bedroom 卧室;}string m_Sittingroom;
private:string m_Bedroom;};//定义Building类后才能定义Person成员函数
void Person::test1(Building *building){cout building-m_Bedroom endl;
}void Person::test(Building *building){cout building-m_Sittingroom endl;
}void test01(){Building *build new Building;Person p;p.test1(build);
}运算符重载及重载实现
运算符重载基本属性
运算符重载的目的是扩展C中提供的运算符的适用范围使之能作用于对象或自定义的数据类型运算符重载的实质是函数重载可以重载为普通函数也可以重载为成员函数运算符重载也是多态的一种和函数重载称为静态多态表示函数地址早绑定在编译阶段就确定好了地址
运算符重载总结
重载运算符()[] - 的时候运算符重载函数必须声明为类的成员函数重载运算符的时候运算符只能通过全局函数配合友元函数进行重载不要重载 和 || 运算符因为无法实现短路原则。
// 重载 运算符
ostream operator(ostream os, cont A a) {// ...return os;
}不能被重载的运算符
sizeof. 成员运算符:: 作用域解析运算符?: 条件运算符typeid 一个RTTI运算符4种强制类型转换运算符
i和i实现
C内置类型的后置返回的是变量的拷贝也就是不可修改的值前置返回的是变量的引用因此可以作为修改的左值。即a或a都可以但a不可以C默认必须修改a的值如果不修改则报错。
//i
int int::operator()
{*this 1return *this
}//i注意后置有占位参数以区分跟前置不同
const int int::operator(int)
{int oldValue *this*thisreturn oldValue
}继承方式、对象模型、同名处理
继承主要是为了减少代码的重复内容解决代码复用问题。通过抽象出一个基类父类将重复代码写到基类中在派生类子类中实现不同的方法。
继承方式
公有继承保持父类中的访问属性私有继承将父类中的所有访问属性改为private保护继承除父类中的私有属性其他改为保护属性
继承的对象模型
子类中会继承父类的私有成员只是被编译器隐藏起来了无法访问到通过sizeof(子类class)可以检查出。子类创建对象时先调用父类的构造函数然后再调用自身的构造析构顺序与构造顺序相反 由于继承中父类和子类的构造、析构顺序原因当父类中只提供了有参构造默认构造等函数会被隐藏而子类仅仅调用默认构造时会因为子类创建对象时无法调用父类构造函数而报错这里可以让子类利用初始化列表来显式调用父类有参构造函数来进行父类构造然后进行子类构造。 子类会继承父类的成员属性和成员函数但子类不会继承父类构造函数和析构函数
继承中的同名处理
父类和子类成员属性同名用子类声明对象调用子类属性若想调用父类成员则加上父类的作用域父类和子类成员函数同名子类函数不会覆盖父类的成员只是隐藏起来用子类声明对象调用子类成员函数若想调用父类函数包括重载则加上父类的作用域若子类中没有与父类同名的成员函数子类声明对象后可以直接调用父类成员函数。
多继承和菱形继承
多继承
多继承会产生二义性的问题。如果继承的多个父类中有同名的成员属性和成员函数在子类调用时需要指定作用域从而确定父类。
菱形继承
两个子类继承于同一个父类同时又有另外一个类多继承于两个子类这种继承称为菱形继承。比如羊和驼继承于动物类同时羊驼继承于羊和驼。
菱形继承会产生问题
**浪费空间。**羊驼继承了两份动物类中的某些数据和函数但只需要一份即可二义性。从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题。 羊驼调用数据和函数时会出现二义性通过sheep类得到一个age通过carmel类得到一个age两个数据不会相互影响相互修改导致同一份数据不一致。
解决菱形继承的问题
使用虚继承在继承方式前加virtual这样的话羊驼可以直接访问m_Age不用添加作用域且这样操作的是共享的一份数据
class Animal{
public:int m_Age;
};
class Sheep:virtual public Animal{int m_sheep;
};
class Camel :virtual public Animal{int m_camel;
};class Son :public Sheep, public Camel{int m_son;
};
void test01(){Son son;son.m_Age 10;cout sizeof(Animal) endl; // 4m_Agecout sizeof(Sheep) endl; // 12sheep-Vbptr,m_sheep,m_Agecout sizeof(Camel) endl; // 12camel-Vbptr,m_camel,m_Agecout sizeof(Son) endl; // 24sheep-Vbptr,m_sheep,camel-Vbptr,m_camel,m_son,m_Age
}**特别注意**此时son没有自己的虚基类表和虚基类指针只是继承了sheep和camel的虚基类指针和虚基类表只是修改了两个虚基类表中的值修改为当前类中如何通过继承的虚基类指针查找虚基类数据Son继承Sheep父类父类中有虚基类指针vbptr(virtual base pointer)对象结构类似结构体首元素是虚基类指针其余为自身数据不包括静态成员和成员函数Sheep的虚基类指针vbptr指向下面Sheep的虚基类表vbtaleSheep(virtual base table)虚基类表是一个整型数组数组第二个元素值为20即Sheep的虚指针地址偏移20指向Animal的m_Age地址。Camel父类同理因此类中只有一个m_Age元素。Son中包含了两个指针和四个int类型所以大小为24。 class Animal{
public:int m_Age;
};
class Sheep:virtual public Animal{int m_sheep;
};
class Camel :virtual public Animal{int m_camel;
};class Son :virtual public Sheep, virtual public Camel{int m_son
};
void test01(){Son son;son.m_Age 10;cout sizeof(Animal) endl; // 4m_Agecout sizeof(Sheep) endl; // 12sheep-Vbptr,m_sheep,m_Agecout sizeof(Camel) endl; // 12camel-Vbptr,m_camel,m_Agecout sizeof(Son) endl; // 28son-vbptr,m_son,m_Age,sheep-Vbptr,m_sheep,camel-Vbptr,m_camel,
}注意跟上面的区别一个是son类中的元素顺序一个是son类有了自己的虚基类指针和虚基类表 虚继承 一般通过虚基类指针和虚基类表实现将共同基类设置为虚基类**每个虚继承的子类虚基类本身没有**都有一个虚基类指针占用一个指针的存储空间和虚基类表不占用类对象的存储空间虚基类指针属于对象虚基类表属于类当虚继承的子类被当做父类继承时虚基类指针也会被继承。虚表中只记录了虚基类数据在派生类对象中与派生类对象首地址(虚基类指针)之间的偏移量,以此来访问虚基类数据虚继承不用像普通多继承那样维持着公共基类虚基类的两份同样的拷贝节省了存储空间。虚基类表本质是一个整型数组
静态函数可以是虚函数吗
不可以因为虚函数属于对象不属于类静态函数属于类
类型兼容性原则 为什么会有多态
类型兼容规则是指在需要基类对象的任何地方都可以使用公有派生类的对象来替代,如使用子类对象可以直接赋值给父类对象或子类对象可以直接初始化父类对象时对于同样的一条语句不管传入子类还是父类对象都是调用的父类函数但我们想实现的是同样的一条语句传入不同的对象调用不同的函数.
class Animal{
public:void speak(){cout Animal speak endl;}
};class Sheep :public Animal{
public:void speak(){ //重定义子类重新定义父类中有相同名称的非虚函数 cout Sheep speak endl;}
};void doSpeak(Animal animal){animal.speak();
}//想通过父类引用指向子类对象
void test01(){Sheep sheep;doSpeak(sheep); //Animal speak;sheep.speak(); //sheep speaksheep.Animal::speak(); //Animal speak; //继承中的重定义可以通过作用域
}但我们想传入子类对象调用子类函数传入父类对象调用父类函数即同样的调用语句有多种不同的表现形态这样就出现了多态
重载、覆盖、重写
重载(overload) 是函数名相同参数列表不同。重载只是在同一个类的内部存在但是不能靠返回类型来判断重载是在编译器期间根据参数类型和个数决定函数调用(静态联编) 重写(override)也叫覆盖子类重新定义父类中有相同名称和参数的虚函数。两者的函数特征相同。 函数重写必须发生在父类与子类之间被重写的函数不能是static的。必须是virtual的重写函数必须有相同的类型名称和参数列表重写函数的访问权限可以不同。尽管virtual是private的子类中重写改写为public,protected也是可以的。 重定义(overwrite)也叫做隐藏。子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。如果一个类存在和父类相同的函数那么这个类将会隐藏其父类的方法除非你在调用的时候强制转换为父类类型或加上父类作用域
多态实现的基础 继承虚函数覆盖父类指针或引用指向子类对象访问虚函数 class Animal{
public:virtual void speak(){ //在父类中声明虚函数可以实现多态动态联编cout Animal speak endl;}int m_age 0;
};class Sheep :public Animal{
public:void speak(){ //发生多态时子类对父类中的成员函数进行重写virtual可写可不写cout Sheep speak endl;}int m_age 1;
};void doSpeak(Animal animal){animal.speak();
}void test01(){//传入子类对象调用子类成员函数Sheep sheep;doSpeak(sheep); //sheep speak;//子类对象直接调用子类成员函数sheep.speak(); //sheep speak;//子类对象通过作用域调用父类成员函数sheep.Animal::speak(); //animal speak;//基类成员不能转换为子类成员即不能向下转换//Animal *animal0 new Animal();//Sheep * sheep0 animal0;//sheep0-speak();//同样不能向下转换//Animal animal0;//Sheep sheep0 animal0;//父类指针指向子类对象Sheep *sheep1 new Sheep();Animal *animal1 sheep1;animal1-speak(); //sheep speak;//父类引用指向子类对象Sheep sheep2;Animal animal2 sheep2;animal2.speak(); //sheep speak;//子类对象直接赋值给父类对象不符合多态条件符合类型兼容性原则Sheep sheep0;Animal animal0 sheep0;animal0.speak(); //animal speak;
}静态多态和动态多态 静态多态运算符重载、函数重载动态多态继承、虚函数 两者主要的区别函数地址是早绑定静态联编还是晚绑定动态联编。即在编译阶段确定好地址还是在运行时才确定地址。
虚函数指针和虚函数表 前提发生了多态每个类中都有虚函数表最开始的父类创建虚函数表后面的子类继承父类的虚函数表然后对虚函数重写虚函数重写覆盖的实质就是重写父类虚函数表中的父类虚函数地址实现多态的流程虚函数指针-虚函数表-函数指针-入口地址虚函数表vftable属于类或者说这个类的所有对象共享一个虚函数表虚函数指针vfptr属于单个对象。在程序调用时先创建对象编译器在对象的内存结构头部添加一个虚函数指针进行动态绑定虚函数指针指向对象所属类的虚函数表。虚函数表是一个指针数组其元素是虚函数的指针每个元素对应一个函数的指针。如果子类对父类中的一个或多个虚函数进行重写子类的虚函数表中的元素顺序会按照父类中的虚函数顺序存储之后才是自己类的函数顺序。编译器根本不会去区分传进来的是子类对象还是父类对象而是关心调用的函数是否为虚函数。如果是虚函数就根据不同对象的vptr指针找属于自己的函数。父类对象和子类对象都有vfptr指针传入对象不同编译器会根据vfptr指针到属于自己虚函数表中找自己的函数。即vptr—虚函数表------函数的入口地址从而实现了迟绑定(在运行的时候才会去判断)。 函数指针与指针函数 指针函数int* f(int x, int y)本质是函数返回值为指针函数指针int (*f)(int x)本质是指针指向函数的指针 通常我们可以将指针指向某类型的变量称为类型指针如整型指针。若将一个指针指向函数则称为函数指针。 函数名代表函数的入口地址同样的我们可以通过根据该地址进行函数调用而非直接调用函数名。
void test001(){printf(hello, world);
}int main(){void(*myfunc)() test001;//将函数写成函数指针myfunc(); //调用函数指针 hello world
}test001的函数名与myfunc函数指针都是一样的即都是函数指针。test001函数名是一个函数指针常量而myfunc是一个函数指针变量这是它们的关系。
函数指针多用于回调函数回调函数最大的优势在于灵活操作可以实现用户定制的函数降低耦合性实现多样性如STL中
C语言实现多态
可以让函数指针指向参数类型相同、返回值类型也相同的函数。通过函数指针我们也可以实现C中的多态。
#includeiostream
typedef int (*func)();int print1(){printf(hello, print1 \n);return 0;
}int print2(){printf(hello, print2 \n);return 0;
}int main(int argc, char * argv[]){func fp print1;fp();fp print2;fp();return 0;
}
怎么理解多态和虚函数 多态的实现主要分为静态多态和动态多态静态多态主要是重载在编译的时候就已经确定动态多态是用虚函数机制实现的在运行期间动态绑定。 举个例子一个父类类型的指针指向一个子类对象时候使用父类的指针去调用子类中重写了的父类中的虚函数的时候会调用子类重写过后的函数
虚函数的实现在有虚函数的类中类的最开始部分是一个虚函数表的指针这个指针指向一个虚函数表表中放了虚函数的地址实际的虚函数在代码段(.text)中。当子类继承了父类的时候也会继承其虚函数表当子类重写父类中虚函数时候会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数会增加访问内存开销降低效率。
构造函数能否实现多态/虚函数指针什么时候初始化
两个问题本质是一样的构造函数不能实现多态 对象在创建时,由编译器对VPTR指针进行初始化只有当对象的构造完全结束后VPTR的指向才最终确定。 子类中虚函数指针的初始化过程 当定义一个子类对象的时候比较麻烦因为构造子类对象的时候会首先调用父类的构造函数然后再调用子类的构造函数。当调用父类的构造函数的时候此时会创建Vptr指针该指针会指向父类的虚函数表然后再调用子类的构造函数子类继承父类的虚函数指针此时Vptr又被赋值指向子类的虚函数表。 也就是说会先调用父类构造函数再调用子类构造函数并不会只调用子类构造函数是没法实现多态的
构造函数能否是虚函数
不能因为在调用构造函数时虚表指针并没有在对象的内存空间中必须要构造函数调用完成后才会形成虚表指针
不要在构造函数中调用虚函数的原因
第一个原因在概念上构造函数的工作是为对象进行初始化。在构造函数完成之前被构造的对象被认为“未完全生成”。当创建某个派生类的对象时如果在它的基类的构造函数中调用虚函数那么此时派生类的构造函数并未执行所调用的函数可能操作还没有被初始化的成员这将导致灾难的发生。
第二个原因即使想在构造函数中实现动态联编在实现上也会遇到困难。这涉及到对象虚指针vptr的建立问题。在Visual C中包含虚函数的类对象的虚指针被安排在对象的起始地址处并且虚函数表vtable的地址是由构造函数写入虚指针的。所以一个类的构造函数在执行时并不能保证该函数所能访问到的虚指针就是当前被构造对象最后所拥有的虚指针因为后面派生类的构造函数会对当前被构造对象的虚指针进行重写因此无法完成动态联编。
#include iostream
using namespace std;class A{
public:A() {show();}virtual void show(){coutin Aendl;}virtual ~A(){}
};class B:public A{
public:void show(){coutin Bendl;}
};int main(){A a;B b;
}不要在析构函数中调用虚函数的原因
析构函数是用来销毁一个对象的在销毁一个对象时先调用该对象所属类的析构函数然后再调用其基类的析构函数所以在调用基类的析构函数时派生类对象的“善后”工作已经完成了这个时候再调用在派生类中定义的函数版本已经没有意义了。
#include iostream
using namespace std;class A{
public:virtual void show(){coutin Aendl;}virtual ~A(){show();}
};class B:public A{
public:void show(){coutin Bendl;}
};int main(){A a;B b;
}
/*
in A
in A
*/抽象类和纯虚函数
在程序设计中如果仅仅为了设计一些虚函数接口打算在子类中对其进行重写那么不需要在父类中对虚函数的函数体提供无意义的代码可以通过纯虚函数满足需求。 纯虚函数的语法格式virtual 返回值类型 函数名 () 0; 只需要将函数体完全替换为 0即可纯虚函数必须在子类中进行实现在子类外实现是无效的。 注意 如果父类中出现了一个纯虚函数则这个类变为了抽象类抽象类不可实例对象如果父类为抽象类子类继承父类后必须实现父类所有的纯虚函数否则子类也为抽象类也无法实例对象但纯虚析构函数例外因为子类不会继承父类的析构函数
虚析构和纯虚析构 仅仅发生继承时创建子类对象后销毁函数调用流程为父类构造函数-子类构造函数-子类析构函数-父类析构函数当发生多态时父类指针或引用指向子类对象通过父类指针在堆上创建子类对象然后销毁调用流程为父类构造函数-子类构造函数-父类析构函数不会调用子类析构函数因此子类中会出现内存泄漏问题。 解决方法将父类中的析构函数设置为虚函数设置后会先调用子类析构函数再调用父类析构函数
纯虚析构 纯虚析构需要类内声明类外实现纯虚析构也是虚函数该类也为抽象类子类不会继承父类的析构函数当父类纯虚析构没有实现时子类不是抽象类可以创建创建对象。
为什么析构函数必须是虚函数
因为当发生多态时父类指针在堆上创建子类对象销毁时会内存泄漏
为什么C默认的析构函数不是虚函数
因为虚函数需要额外的虚函数表和虚表指针占用额外的内存。而对于不会被继承的类来说其析构函数如果是虚函数就会浪费内存
类模板和函数模板
通过template或template实现主要用于数据的类型参数化简化代码有类模板和函数模板函数模板是用于生成函数的类模板则是用于生成类的
类模板和函数模板定义 template声明下面是函数定义则为函数模板否则为类模板。注意每个函数模板前必须有且仅有一个template声明不允许多个template声明后只有一个函数模板也不允许一个template声明后有多个函数模板(类模板同理)。 类模板与函数模板的区别 类模板不支持自动类型推导数据类型可以有默认参数. 文章转载自: http://www.morning.wjhdn.cn.gov.cn.wjhdn.cn http://www.morning.jsdntd.com.gov.cn.jsdntd.com http://www.morning.nfbnl.cn.gov.cn.nfbnl.cn http://www.morning.gbcnz.cn.gov.cn.gbcnz.cn http://www.morning.zwppm.cn.gov.cn.zwppm.cn http://www.morning.cmrfl.cn.gov.cn.cmrfl.cn http://www.morning.ychoise.com.gov.cn.ychoise.com http://www.morning.qtrlh.cn.gov.cn.qtrlh.cn http://www.morning.nnpfz.cn.gov.cn.nnpfz.cn http://www.morning.fpkpz.cn.gov.cn.fpkpz.cn http://www.morning.ksqzd.cn.gov.cn.ksqzd.cn http://www.morning.wyjpt.cn.gov.cn.wyjpt.cn http://www.morning.cwgfq.cn.gov.cn.cwgfq.cn http://www.morning.fhsgw.cn.gov.cn.fhsgw.cn http://www.morning.ysfj.cn.gov.cn.ysfj.cn http://www.morning.brwwr.cn.gov.cn.brwwr.cn http://www.morning.rmlz.cn.gov.cn.rmlz.cn http://www.morning.mnygn.cn.gov.cn.mnygn.cn http://www.morning.nkjjp.cn.gov.cn.nkjjp.cn http://www.morning.smrty.cn.gov.cn.smrty.cn http://www.morning.qpmmg.cn.gov.cn.qpmmg.cn http://www.morning.hjsrl.cn.gov.cn.hjsrl.cn http://www.morning.sgcdr.com.gov.cn.sgcdr.com http://www.morning.ngcsh.cn.gov.cn.ngcsh.cn http://www.morning.xpmwt.cn.gov.cn.xpmwt.cn http://www.morning.wjjxr.cn.gov.cn.wjjxr.cn http://www.morning.cwwts.cn.gov.cn.cwwts.cn http://www.morning.zlnf.cn.gov.cn.zlnf.cn http://www.morning.wbhzr.cn.gov.cn.wbhzr.cn http://www.morning.kwqwp.cn.gov.cn.kwqwp.cn http://www.morning.bsjxh.cn.gov.cn.bsjxh.cn http://www.morning.qjbxt.cn.gov.cn.qjbxt.cn http://www.morning.zrnph.cn.gov.cn.zrnph.cn http://www.morning.nwclg.cn.gov.cn.nwclg.cn http://www.morning.rqxhp.cn.gov.cn.rqxhp.cn http://www.morning.dzrcj.cn.gov.cn.dzrcj.cn http://www.morning.rkjz.cn.gov.cn.rkjz.cn http://www.morning.pmmrb.cn.gov.cn.pmmrb.cn http://www.morning.sjwws.cn.gov.cn.sjwws.cn http://www.morning.gnlyq.cn.gov.cn.gnlyq.cn http://www.morning.rshs.cn.gov.cn.rshs.cn http://www.morning.redhoma.com.gov.cn.redhoma.com http://www.morning.yrqb.cn.gov.cn.yrqb.cn http://www.morning.sqqdy.cn.gov.cn.sqqdy.cn http://www.morning.tlrxp.cn.gov.cn.tlrxp.cn http://www.morning.bpmfg.cn.gov.cn.bpmfg.cn http://www.morning.rgkd.cn.gov.cn.rgkd.cn http://www.morning.ybgcn.cn.gov.cn.ybgcn.cn http://www.morning.mypxm.com.gov.cn.mypxm.com http://www.morning.qrgfw.cn.gov.cn.qrgfw.cn http://www.morning.rxfgh.cn.gov.cn.rxfgh.cn http://www.morning.qxlxs.cn.gov.cn.qxlxs.cn http://www.morning.tpchy.cn.gov.cn.tpchy.cn http://www.morning.wslr.cn.gov.cn.wslr.cn http://www.morning.bpmnc.cn.gov.cn.bpmnc.cn http://www.morning.bsbcp.cn.gov.cn.bsbcp.cn http://www.morning.pylpd.cn.gov.cn.pylpd.cn http://www.morning.dkmzr.cn.gov.cn.dkmzr.cn http://www.morning.bwttp.cn.gov.cn.bwttp.cn http://www.morning.rfmzs.cn.gov.cn.rfmzs.cn http://www.morning.skkln.cn.gov.cn.skkln.cn http://www.morning.mdmqg.cn.gov.cn.mdmqg.cn http://www.morning.yzfrh.cn.gov.cn.yzfrh.cn http://www.morning.nwrzf.cn.gov.cn.nwrzf.cn http://www.morning.nzcgj.cn.gov.cn.nzcgj.cn http://www.morning.dsmwy.cn.gov.cn.dsmwy.cn http://www.morning.mrskk.cn.gov.cn.mrskk.cn http://www.morning.tmfhx.cn.gov.cn.tmfhx.cn http://www.morning.hsrpc.cn.gov.cn.hsrpc.cn http://www.morning.smcfk.cn.gov.cn.smcfk.cn http://www.morning.xtlty.cn.gov.cn.xtlty.cn http://www.morning.qwbls.cn.gov.cn.qwbls.cn http://www.morning.prqdr.cn.gov.cn.prqdr.cn http://www.morning.ggxbyhk.cn.gov.cn.ggxbyhk.cn http://www.morning.xdfkrd.cn.gov.cn.xdfkrd.cn http://www.morning.pdkht.cn.gov.cn.pdkht.cn http://www.morning.gwdmj.cn.gov.cn.gwdmj.cn http://www.morning.jzlkq.cn.gov.cn.jzlkq.cn http://www.morning.kbbmj.cn.gov.cn.kbbmj.cn http://www.morning.cykqb.cn.gov.cn.cykqb.cn