2024/01/10

回顾C++

目录

1. 类中成员变量和成员函数分开存储

1.1 空对象占用的空间:1字节

1.2 有非静态成员变量的类:根据变量大小

C++类中的内存对齐

1.3 有静态成员变量的类:静态成员变量不占空间

1.4 非静态成员函数的类:函数不占用空间

1.5 静态成员函数的类:函数不占用空间

1.6 普通继承

1.7 虚拟继承

1.8 延申到this指针:

2. this指针

2.1 用途:

2.2 链式编程:

3. 常函数和常对象

3.1 常函数

3.2 mutable关键字

3.3 常对象

4. 友元

4.1 全局函数做友元: friend void func();

4.2 类做友元:friend class Friend;

例子:一个类Friend可以访问Person中的私有成员age

BUG: c++出现 “no default constructor exists for class”的问题

例子2:Friend访问Building中的私有成员bedroom

4.3 成员函数做友元:friend void Friend::func();

5. 继承

5.1 基本语法

5.2 三种继承方式

5.3 继承时构造析构顺序以及同名成员的处理方式

5.3.1 结论

5.3.2 同名成员变量

5.3.3 同名成员函数

函数重定义/隐藏(之后继续补充)

5.3.4 同名静态成员变量

5.3.5 同名静态成员函数

5.4 多继承

5.4.1 二义性

5.5 菱形继承

5.5.1 出现的问题:

5.5.2 虚继承

参考:

1. 重写/重载(覆盖)/重定义(隐藏):


1. 类中成员变量和成员函数分开存储

  • c++中,类内成员变量和成员函数分开储存

  • 只有非静态的成员变量才属于类的对象上

class Person
{
	int m_A;				//非静态成员变量,属于类的对象上
	static int m_B;			//静态成员变量,不属于类的对象上
	void func(){}			//非静态成员函数,不属于类的对象上
	static void func2(){}	//静态成员函数,不属于类的对象上
}

1.1 空对象占用的空间:1字节

相当于用书占位

class Person
{

};

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//1
}

int main()
{
	test();
}

原因:

C++编译器会给每个空对象也分配一个字节空间,为了区分空对象占内存的位置

每个空对象应该有一个独一无二的内存地址

1.2 有非静态成员变量的类:根据变量大小

class Person
{
	int m_a;	//属于类的对象上
};

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//4
}

int main()
{
	test();
}

非静态成员变量大小为4,所以类占用的大小为4.

C++类中的内存对齐

C++中的类的对齐的字节,并不是一个定数,而是以类中的成员变量占用的字节数最大的类型作为对齐标准的

例子1:

class Person
{
    char ch;
	int m_a;	//属于类的对象上
};

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//8
}

int main()
{
	test();
}

结果是8,说明以4个字节作为对齐

例子2:

class MyClass
{
private:
	char b;     // 1
	double c;    // 8
};

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//16
}

int main()
{
	test();
}

结果是16,说明现在的内存对齐的标准又变成了8个字节了

1.3 有静态成员变量的类:静态成员变量不占空间

class Person
{
	static int m_a;	//静态成员变量,不属于类的对象上
};

int Person::m_a = 0;

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//1
}

int main()
{
	test();
}

结果是1(相当于只有一个空类)

1.4 非静态成员函数的类:函数不占用空间

成员函数不占用空间,不属于类的对象上

class Person
{
	void func(){}
};

void test()
{
	Person p;
	cout<<"sizeof p is "<< sizeof(p)<<endl;	//1
}

int main()
{
	test();
}

1.5 静态成员函数的类:函数不占用空间

1.6 普通继承

class A
{
    int a;
};
class B
{
  int b;
};
class C : public A, public B
{
  int c;
};

结果为12.说明普通继承就是基类的大小加上派生类自身成员大小

1.7 虚拟继承

class A
{
    int a;
};
class B
{
    int b;
};

class C : virtual public A, virtual public B
{
    int c;
};

class D : virtual public A
{
    int d;
};

void test2()
{
    //A a;
    cout<<sizeof(int)<<endl;    //4
    
    C c;
    cout<<sizeof(c)<<endl;      //24

    D d;
    cout<<sizeof(d)<<endl;      //16
}

int main(int argc, char const *argv[])
{
    test2();
    return 0;
}

当存在虚拟继承时,派生类中会有一个指向虚基类表的指针。所以其大小应为普通继承的大小,再加上虚基类表的指针大小(32位4个字节,上图例子64位8个字节)。

1.8 延申到this指针:

从上面可以看出,因为成员函数只会诞生一份函数实例,所以多个同类型对象会共用一部分代码。那么需要this指针来区分哪个对象在调用自己。

2. this指针

2.1 用途:

  • 形参和实参同名时加以区分
  • 在非静态成员函数的返回值为对象本身时,使用*this

2.2 链式编程:

class Person
{
public:
    Person(int age)
    {
        this->age = age;
    }
    Person& addAge(Person& p)
    {
        this->age += p.age;
        return *this;
    }

    int age;	//属于类的对象上
};

void test()
{
	Person p(10);
    Person p2(20);

    cout<< p.addAge(p2).age <<endl;     //30
    cout<< p.addAge(p2).addAge(p2).age <<endl;  //70
}

int main()
{
	test();
}

注意:Person& addAge(Person& p)中,

  • 参数使用引用的原因:传入地址,节约空间
  • 返回值使用引用的原因:相当于返回它的本身,从而可以实现一直addAge。如果不是引用,结果为20.

3. 常函数和常对象

3.1 常函数

一个类中,如果成员函数后面加上const关键字,则该函数成为常函数。

常函数不可以修改成员变量的值。

class Person
{
public:
    void printAge() const
    {
        cout<<"age is "<< m_age <<endl;
    }

    int m_age;
};

原因:m_age相当于this->m_age,但是this是一个指针常量(可以改变值,但不能改变指向),再加上一个const,导致值也不能改变。

3.2 mutable关键字

如果成员变量前面有mutable关键字,则可以在常函数中修改这个成员变量的值。

class Person
{
public:
    void printAge() const
    {
        m_age += 1;
        cout<<"age is "<< m_age <<endl;
    }

    mutable int m_age;
};

3.3 常对象

在声明一个对象时,前面加上const关键字。

  • 常对象中的成员变量不可以被修改,但可以被访问。
  • 常对象只能访问被 const 修饰的成员函数
  • mutable修饰的成员变量可以在常对象中被修改

class Person
{
public:
	void showPerson() const
	{
		m_B = 200;		//可以修改
	}
	
	void func()
	{
		m_A= 200;
	}
	
	int m_A;
	mutable int m_B;	//加关键字
};

void test()
{
	const Person p;
	p.m_A = 100;	//报错
	p.m_B = 300;	//可以修改
	
	p.showPerson();	//可以,因为是常函数
	p.func();		//报错
}

注意:有些编译器g++中,不能直接声明const Person p ,需要自己写构造函数

#include <iostream>

using namespace std;

class Person
{
public:
    Person(int a, int b):m_A(a),m_B(b){}

    int m_A;
	mutable int m_B;	//加关键字

	void showPerson() const
	{
		m_B = 200;		//可以修改
	}
	
	void func()
	{
		m_A= 200;
        cout<<"haha"<<endl;
	}
};

void test()
{
	const Person p(10,20);
	//p.m_A = 100;	//报错
	p.m_B = 300;	//可以修改
	
    p.showPerson();
    //p.func();		//报错

    cout<<p.m_A<<endl;  //可以访问
    cout<<p.m_B<<endl;  //可以访问
}

int main()
{
    test();
}

4. 友元

让一个函数/一个外部的类访问当前类中的私有成员。

关键字:friend,注意要在当前类中声明该函数/类。

三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.1 全局函数做友元: friend void func();

#include <iostream>
using namespace std;

class Person
{
    friend void getAge(Person& p);
public:
    Person(int age)
    {
        this->age = age;
    }
private:
    int age;
};

void getAge(Person& p)
{
    cout<<p.age<<endl;
    return;
}

void test()
{
	Person p1(10);
    getAge(p1);
}

int main()
{
    test();
}

4.2 类做友元:friend class Friend;

例子:一个类Friend可以访问Person中的私有成员age
#include <iostream>
#include <string.h>

using namespace std;

class Person
{
    friend class Friend;
public:
    Person(string name, int age)
    {
        this->name = name;
        this->age = age;
    }
public:
    string name;
private:
    int age;
};

class Friend
{
public:
    Friend(string name,Person* person)
    {
        this->name = name;
        p = person;
    }

    void printAge()
    {
        cout<<p->name<<endl;
        cout<<p->age<<endl;
    }

    string name;
    Person *p;
};

void test()
{
	Person p("henry",20);
    Friend f("Bill",&p);

    f.printAge();
}

int main()
{
    test();
}
BUG: c++出现 “no default constructor exists for class”的问题

当自己定义有参构造时,默认的无参构造函数将被覆盖

c++,出现 “no default constructor exists for class”的问题-CSDN博客

例子2:Friend访问Building中的私有成员bedroom
#include <iostream>
#include <string.h>

using namespace std;

class Building
{
    friend class Friend;
public:
    Building();
private:
    string bedroom;
};

Building::Building()
{
    bedroom = "br";
}

class Friend
{
public:
	Friend();
	void visit();	//参观函数,访问Building中的属性
    Building * building;
};

Friend::Friend()
{
    building = new Building;
}

void Friend::visit()
{
    cout<<"visiting: "<<building->bedroom<<endl;
}

void test()
{
	Friend f;
	f.visit();
}

int main()
{
    test();
}

4.3 成员函数做友元:friend void Friend::func();

例子:

#include <iostream>
#include <string.h>

using namespace std;

class Person;

class Friend
{
public:
    Friend(string name, Person* p);
    void getAge();

    string name;
    Person *p;
};

Friend::Friend(string name, Person* p)
{
    this->name = name;
    this->p = p;
}

class Person
{
    friend void Friend::getAge();
public:
    Person(string name, int age);
    string name;
private:
    int age;
};

Person::Person(string name, int age)
{
    this->name = name;
    this->age = age;
}

void Friend::getAge()
{
    cout<<p->name<<endl;
    cout<<p->age<<endl;
}

int main(int argc, char const *argv[])
{
    /* code */
    Person p("Henry", 10);
    Friend f("Bill",&p);
    f.getAge();
    return 0;
}

注意:void Friend::getAge()必须定义在Person结束后,不然会报错:

error: invalid use of incomplete type 'class Person'
     cout<<p->name<<endl;

以及

error: invalid use of incomplete type 'class Person'
     cout<<p->age<<endl;

5. 继承

5.1 基本语法

class 子类: 继承方式 父类

5.2 三种继承方式

public,protected,private

此时如果有另一个class C无论以何种方式继承B,都无法获取B类的a,b,c元素。

5.3 继承时构造析构顺序以及同名成员的处理方式

5.3.1 结论

直接访问(用.)则是子类自身的成员变量/函数,如果要访问基类,需要加上作用域运算符::

5.3.2 同名成员变量
5.3.3 同名成员函数

如果子类中出现和父类同名的成员函数,子类会隐藏掉父类所有的同名成员函数。

注意,与函数重载不同,因为作用域不一样。

函数重定义/隐藏(之后继续补充)
  1. 不在同一个作用域(分别位于派生类与基类) ;
  2. 函数名字相同;
  3. 返回值可以不同;
  4. 参数不同。此时,不论有无 virtual 关键字,基类的函数将被隐藏(注意别与重载以及覆盖混淆) 
  5. 参数相同,但是基类函数没有 virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆) 
#include <iostream>

using namespace std;

class Base
{
public:
    Base()
    {
        cout<<"Base gouzao"<<endl;
        m_A = 10;
    }

    void getNum()
    {
        cout<<"m_A is: "<<m_A<<endl;
        return;
    }

    ~Base()
    {
        cout<<"Base xigou"<<endl;
    }

    int m_A;
};


class Child: public Base
{
public:
    Child()
    {
        cout<<"Child gouzao"<<endl;
        m_A = 20;
    }

    int getNum()
    {
        cout<<"m_A is: "<<m_A<<endl;
        return m_A;
    }

    ~Child()
    {
        cout<<"Child xigou"<<endl;
    }

    int m_A;
};

void test()
{
    Child c;

    cout<< c.m_A <<endl;        //20
    cout<< c.Base::m_A <<endl;  //10

    int b = c.getNum();         //m_A is: 20
    cout<< b <<endl;            //20
    c.Base::getNum();           //m_A is: 10
}

int main(int argc, char const *argv[])
{
    /* code */
    test();
    return 0;
}

输出:

Base gouzao
Child gouzao
20
10
m_A is: 20
20
m_A is: 10
Child xigou
Base xigou
5.3.4 同名静态成员变量

回顾静态成员变量:所有类共享同一个变量,类内声明类外初始化,编译阶段分配内存,两种访问方式(类名和对象)

继承中处理同名静态成员变量与正常的成员变量方法一致。

唯一的额外写法:获取子类的基类中的某个成员变量

Child::Base::m_A;
#include <iostream>

using namespace std;

class Base
{
public:
    static int m_A;
};
int Base::m_A = 10;

class Child: public Base
{
public:
    static int m_A;
};
int Child::m_A = 20;

void test()
{
    Child c;
    cout<<c.m_A<<endl;            //20
    cout<<c.Base::m_A<<endl;        //10

    cout<<Child::m_A<<endl;        //20
    cout<<Child::Base::m_A<<endl;    //10

    cout<<Base::m_A<<endl;            //10
}

int main(int argc, char const *argv[])
{
    /* code */
    test();
    return 0;
}

5.3.5 同名静态成员函数

回顾静态成员函数:所有类共享同一个函数,只能访问静态成员变量

继承中同名静态成员函数方法和静态成员变量类似。

Child c;
c.getNum();
c.Base::getNum();

Child::getNum();
Base::getNum();
Child::Base::getNum();

5.4 多继承

语法:

class 子类: 继承方式 父类1, 继承方式 父类2, ...

多继承容易导致同名成员出现,所以实际中不建议使用。

5.4.1 二义性

两个父类有同名的成员,此时子类访问时,需要作用域运算符来加以区分

5.5 菱形继承

有两个类继承同一个基类,又有一个派生类多继承这两个类。

5.5.1 出现的问题:

派生类会出现二义性(无论是成员函数还是成员变量)(需要加作用域区分),以及派生类会重复继承一些数据(资源浪费)。

5.5.2 虚继承

解决菱形继承的问题

语法:

class A {};
class B: virtual public A {};
class C: virtual public A {};
class D: public B, public C {};

B和C虚继承,A为虚基类。D中相同属性只会保留一份数据。

虚基类指针:vbptr,指向对应的虚基类表:vbtable

#include <iostream>
using namespace std;

class A
{
public:
    int m_A;
};

class B: public A
{
public:
    int m_B;    
};

class C: public A
{
public:
    int m_C;
};

class D: public B, public C
{
public:
    int m_D;
};

int main(int argc, char const *argv[])
{
    /* code */
    D d;
    // d.m_A = 10;  //报错,不明确,因为B,C都有该m_A属性
    d.B::m_A = 10;
    // d.A::m_A = 20;  //报错,不明确,孙子无法直接访问基类
    d.C::m_A = 20;

    cout<<d.B::m_A<<endl;   //10
    cout<<d.C::m_A<<endl;   //20

    return 0;
}

参考:

1. 重写/重载(覆盖)/重定义(隐藏):

https://www.cnblogs.com/tanky_woo/archive/2012/02/08/2343203.html

  • 18
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值