函数高级:
函数的声明和实现只能有其一带有缺省值,看网上说貌似是标准,防止编译和链接时出现歧义
占位参数,调用时必须要用相应参数
函数重载,函数返回类型无法作为定义函数唯一性的要素
类和对象:
1.访问权限
类内均可以访问
public 类外也可以访问
protected 使得子类可以访问父类的内容
private 子类也无法访问父类的内容
继承类型代表了底线,未达到底线的成员自动拉到底线(public->protected->private)
2.构造类的方法
①分为括号法、显式法、隐式转换法
Person p(参数)、Person p = Person(参数)、Person p = {(参数)} (单个参数可以不用括号)
没有构造函数的情况下,提供默认无参构造函数以及默认拷贝构造函数。
没有有参构造函数的情况下,提供默认拷贝构造函数。
有拷贝构造函数的情况下,不提供默认无参构造函数及其他默认构造函数。
初始化列表法示例:
Person(传参):成员(对应参数),成员(对应参数), ... ,成员(对应参数){
执行语句
}
②
先构造类的对象,再构造自身,没啥好说的。
如果类的成员对象不是指针(不在堆区),先析构成员对象,再析构类自身,遵循栈的先进后出。
3.常对象只能调用常函数成员
以 int getX() const 常函数为例将之作解析,即 int getX(const Coordinate * this)
必须接收常量指针,反之一个道理,因为不能用非常量指针指向常量指针,所以常量类不能调用非常量成员函数
常函数的const修饰词只能用于类的成员函数,该修饰词可以用作函数重载(实际就是参数不同,因为this指针类型发生了变化)
来点没用的代码:
#pragma once
#include "dependecies.h"
class Person
{
private:
int age;
public:
int get_age();
int get_age() const;
Person(int,string);
Person(const Person&);
public:
void operator=(const Person&);
};
#include "Person.h"
Person::Person(int _age=1,string __age="123") {
age = _age;
}
Person::Person(const Person& a) {
cout << "const Person &a" << endl;
age = a.age;
}
int Person::get_age() {
cout << age << endl;
return age;
}
int Person::get_age() const{
cout << age << endl;
return age;
}
void Person::operator=(const Person& a) {
cout << "wow" << endl;
age = a.get_age();
}
#include"Person.h"
using namespace std;
int main() {
Person P2(10,"123");
Person P3 = P2;
Person P4 = { 1,"123" };
Person* P5 = new Person(P4);
P3 = P2;
system("pause");
}
/*输出
const Person &a
const Person &a
wow
10
*/
4.深拷贝与浅拷贝
区别在于是不是单纯的值传递,对于深拷贝(手动实现),拷贝指针变量时,会另外分配内存空间存储相同数据。默认拷贝函数使用浅拷贝,浅拷贝可能带来执行析构函数时,因为重复释放指针指向内存的报错问题。
此外,赋值运算符重载也会让
5.静态成员变量、函数
静态成员函数不可以访问非静态成员
不能通过静态与非静态重载具有相同参数的成员函数
静态成员函数也可以通过类的实例访问
6.空对象、空指针
空对象(准确的说是没有任何成员的类,不是NULL)也占用字节,以为了标记在内存中的位置
决定对象占用字节数的因素:
类的非静态成员变量,当该成员变量为类时,该成员类占用字节规则相同,但对于类来说,将至少创建4个字节的空间分配到该成员类。(不知道为啥)
疑问:类的非静态成员函数为啥不占用字节?
答:所有相同类的对象共享的是同一份非静态成员函数。即类的非静态成员函数是不依赖于对象存在的。该非静态成员函数中需要的所有的成员数据都是通过this指针获取的。
#pragma once
#include "dependecies.h"
class Child
{
public:
Child() {
cout << "building child..." << endl;
}
~Child() {
cout << "deleting child..." << endl;
}
};
#pragma once
#include "dependecies.h"
#include "Child.h"
class Person
{
private:
Child child;
int age;
public:
Person(int );
Person{
cout << sizeof(child) << endl;
};
~Person();
int get_age();
int get_age2();
int get_age() const;
int& get_num();
static int get_static_num();
static int num;
public:
void operator=(const Person&);
};
#include"Person.h"
using namespace std;
int main() {
Person P2;
cout << sizeof(P2) << endl;
}
#######输出#######
1//Child空对象占用字节
8//Person空对象占用字节:age(4字节)+Child(4字节)
对于类的空指针,如果访问的成员函数没有调用this,则可以正常调用该成员函数,因为不管是静态还是非静态成员函数,该类的所有对象都共享一份
mutable修饰词,使得常函数中也可以修改类的该成员变量
7.友元函数
用法:使用friend修饰你想要访问类内私有成员的函数(全局函数、成员函数、类)
friend + 函数声明,friend class +类名
声明友元类不需要include该类的头文件,相当于只是声明了该类的名称,存不存在不是必要的。对成员函数同理,仅需要声明友元以及该成员函数,不需要定义成员函数,当然前提是你没调用这个成员函数。
必须将先定义的类的成员函数作为后定义类的友元函数,调换顺序会出现语法错误(太SB了)
解决方法:当将一个类A的成员函数A::F设置成类B的友元时,需要预先定义类A,否则不能将A::F指定为友元。而在定义B之后,才能定义A::F,因为A::F被设为 友元正是为了访问类B的成员函数。所以,可以按照如下的顺序:
- 声明类B
- 定义类A,声明但不实现A::F
- 定义类B,设置A::F为友元
- 实现A::F
全局友元函数和成员友元函数具有相同功能时,先使用成员友元函数,
8.运算符重载
类内运算符默认输入的第一个参数是类类型,若想要变更需采用全局函数进行运算符重载
左移运算符必须全局函数声明,个人认为是因为第一个参数的类型是输出流,而类内运算符重载第一个参数固定为该类实例
输出输入流全局只能有一个,所以要加引用 例:
void operator<<(ostream &cout,Person& p)
标准输出流是一个全局性的被所有线程共享的对象。 该标准不提供任何保证 std::cout 可以安全地从多个线程访问。 因此,访问标准输出流必须同步:在任何时候,只有一个线程可以访问 std::cout。
递增运算使用占位运算表示后置,因为有人认为后置递增运算符其实不算单目运算符,所以用占位参数表示他是双目运算符,即后置递增。
赋值运算符默认浅拷贝,所以针对含有指针的类进行拷贝时可能存在重复析构的风险
9.继承
①基类和派生类存在成员变量或函数同名情况
加上作用域,否则默认调用当前类的成员
②虚基类
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
针不戮 以及 原理https://blog.csdn.net/Hsuxu/article/details/7444241/
③多态
静态多态和动态多态
静态多态的函数在编译阶段就确定了地址
动态多态的函数在运行阶段确定地址
小测试:
继承顺序:
A->Base->Derive->DeriveB
A->BaseB->Derive->DeriveB
函数分布:
A:func1,func2
Base:func1,func2,func3
BaseB:func1
Derive:func1,func4,func5
DeriveB:func4,func5
所有函数均为虚函数,无参数,返回类型为void
在Base和BaseB对A虚继承的情况下:
#include"Derive.h"
#include"DeriveB.h"
using namespace std;
int main() {
Base base = Base();
char buffer[33];
void(*c)(void) = NULL;
DeriveB derive_b;
Derive *derive = &derive_b;
Base* b = &derive_b;
BaseB* bb = &derive_b;
A* a = &derive_b;
_itoa_s(*(int*)a, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)b, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)bb, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)derive, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)&derive_b, buffer, 16);
cout << buffer << endl;
for (int i = 0; i < 2; i++) {
c = (void(*)(void)) * ((int*)*((int*)(a)) + i);
c();
cout << (int)c << endl;
c = (void(*)(void)) * ((int*)*(int*)(b) + i);
c();
cout << (int)c << endl;
c = (void(*)(void)) * ((int*)*(int*)(derive) + i);
c();
cout << (int)c << endl;
//c = (void(*)(void)) * ((int*)*(int*)(bb) + i);
//c();
//cout << (int)c << endl;
//运行该注释部分会报错,原因在于通过BaseB首地址访问的内容并不是虚函数指针
c = (void(*)(void)) * ((int*)*(int*)(&derive_b) + i);
c();
cout << (int)c << endl;
}
return 0;
}
输出结果:
各个虚表的地址:
c1bc60
c1bc4c
c1bc78 //这个地址的存储对象不是BaseB虚表地址,按循环内部的注释访问会报错
c1bc4c
c1bc4c
通过虚函数指针访问函数:(访问顺序 A,Base,Derive,DeriveB)
i=0时:
Derive::func1
12653614
Base::func3
12652979
Base::func3
12652979
Base::func3
12652979
i=1时:
Base::func2
12653814
DeriveB::func4
12653249
DeriveB::func4
12653249
DeriveB::func4
12653249
本例中一共存在2张虚表,分别为A的一张虚表(存储2个函数地址,分别为func1-func2)以及Base->Derive->DeriveB共享的一张虚表(存储3个函数地址,分别为func3-func5),BaseB没有虚函数指针指向虚表
由输出可知,虚继承的基类若不存在新的虚函数,则不创建虚函数指针,若存在,则创建指向该新虚函数的虚函数指针,换句话说,如果BaseB存在新的虚函数实现,本例共会有3张虚表,但多出的虚表仅供BaseB(存储一个func4地址),Base->Derive->DeriveB依然共享一张虚表(没有func4地址)
在Base和BaseB对A不是虚继承的情况下:
#include"Derive.h"
#include"DeriveB.h"
using namespace std;
int main() {
Base base = Base();
char buffer[33];
void(*c)(void) = NULL;
DeriveB derive_b;
Derive *derive = &derive_b;
Base* b = &derive_b;
BaseB* bb = &derive_b;
//A* a = &derive_b;
//_itoa_s(*(int*)a, buffer, 16);
// cout << buffer << endl;
_itoa_s(*(int*)b, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)bb, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)derive, buffer, 16);
cout << buffer << endl;
_itoa_s(*(int*)&derive_b, buffer, 16);
cout << buffer << endl << endl;
for (int i = 0; i < 2; i++) {
/*c = (void(*)(void)) * ((int*)*((int*)(a)) + i);
c();
cout << (int)c << endl << endl;*/
c = (void(*)(void)) * ((int*)*(int*)(b) + i);
c();
cout << (int)c << endl;
c = (void(*)(void)) * ((int*)*(int*)(derive) + i);
c();
cout << (int)c << endl;
c = (void(*)(void)) * ((int*)*(int*)(bb) + i);
c();
cout << (int)c << endl;
c = (void(*)(void)) * ((int*)*(int*)(&derive_b) + i);
c();
cout << (int)c << endl;
cout << endl;
}
return 0;
}
28bdb0
28bc60
28bdb0
28bdb0
通过虚函数指针访问函数:(访问顺序 Base,BaseB,Derive,DeriveB)
Derive::func1
2626216
Derive::func1
2626216
Derive::func1
2626986
Derive::func1
2626216
Base::func2
2625871
Base::func2
2625871
A::func2
2625616
Base::func2
2625871
在这个情况下,一共存在2张虚表,而且即使BaseB没有定义新的虚函数,BaseB依然存在虚函数指针指向一张别的虚表,该表存储2个函数地址(func1,func2),而A->Base->Derive->DeriveB共享一张虚表(func1,func2,func3,func4,func5)。应该注意到,在A中由Base覆盖的func2在BaseB的虚表中没有被覆盖,并且,Base中的func1地址也和BaseB中的func1地址不一样,原因在于本例没有虚继承,所以在该例中其实存在2份A
总结:
在VS2019环境下进行测试
如果不是虚继承,则继承链共享一张虚表,当然,如果存在多继承情况,有n个父类则会多出(n-1)张虚表,多出的虚表仅供该父类使用,继承链沿第一个继承的父类向下
如果是虚继承,则根据虚继承的子类是否存在新的虚函数来决定是否有新的虚表(仔细想想这种情况下似乎也不用这张虚表)。
此外,测试了Base和BaseB均无新虚函数的情况,这种情况下,Derive采取虚继承情况下创建虚表的规则(与A进行对比是否有新虚函数)。
③纯虚函数、抽象类
含有纯虚函数(包括析构函数)的类被称为抽象类,无法进行实例化,如果子类没有对父类的纯虚函数进行多态重写,则也不允许实例化
虚析构的作用是为了用父类指针执行子类的析构函数(具体原理???)