95-142 类和对象

函数高级:

函数的声明和实现只能有其一带有缺省值,看网上说貌似是标准,防止编译和链接时出现歧义

占位参数,调用时必须要用相应参数

函数重载,函数返回类型无法作为定义函数唯一性的要素

类和对象:

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进行对比是否有新虚函数)。

③纯虚函数、抽象类

含有纯虚函数(包括析构函数)的类被称为抽象类,无法进行实例化,如果子类没有对父类的纯虚函数进行多态重写,则也不允许实例化

虚析构的作用是为了用父类指针执行子类的析构函数(具体原理???)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值