C++面向对象

C++面向对象

一、C和C++关于数据和函数

image-20240106200318374 image-20240106200318374

不同类型的数据创建相应的变量,函数就是处理这些变量。

C中的数据是共享的,其他的函数也可以处理这些数据。

C++将数据和函数包起来,放在类或结构体里面,以此为整体创建对象。

二、Object Based(基于对象) 和 Object Oriented(面向对象)

Object Based:面对的是单一class的设计

Object Oriented: 面对的是多重classes的设计,classes和classes之间的关系

三、Classes的两个经典分类

1.不包含指针的class

2.包含指针的class

四、C++代码的基本形式

延伸文件名不一定是.h或.cpp,也可能是.hpp或其他或甚至无延伸名(看平台)。

五、头文件

1.头文件中的防卫式声明

程序第一次include时,如果没有定义过_ COMPLEX _,就定义,否则不进入这个头文件内容。

因为可能很多程序都会用到这个头文件,使用防卫式声明的目的就是避免重复引入。

2.头文件的布局

六、类的声明和定义

1.类模板template

template的作用是在类声明的时候不完全写死一些类型,而是在创建对象的时候指定类型。这样的好处是类更通用。

2.inline(内联)函数

C/C++为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

inline const char *num_check(int v)
{
return (v % 2 > 0) ? "奇" : "偶";
}

int main(void)
{
int i;
for (i = 0; i < 100; i++)
printf("%02d %s\n", i, num_check(i));
return 0;
}

for循环中num_check(i)会被替换为 (i%2>0)?”奇”:”偶”,这样就避免了频繁调用函数对栈内存重复开辟所带来的消耗。

函数如果在类体中定义完成,就可能是inline函数(是否是inline函数得看编译器,有些复杂的函数编译器无法将其转为inline函数)。

如果函数在类体中声明,在类外定义,也可以在定义该函数时,在该函数前面加inline关键字;告诉编译器尽量将其转为inline函数。

3.访问级别

public: 公有,外界可以通过类对象的成员访问符访问

private: 私有,外界无法直接通过类对象的成员访问符来访问

protected:

4.构造函数

在创建对象时,会自动调用构造函数。

构造函数的写法(2种)

默认实参不只是构造函数可以有,其他函数也可以这样写。

初始值和初始列这种写法只有构造函数才有,写构造函数时也建议用这种写法,这种写法相比于赋值的写法效率高一些。

函数重载-overloading,之所以可以重载,是因为函数编译后的实际名称并不是我们所写的那个函数名,而是加了对形参,返回值等的编码构成的函数名。

构造函数可以有很多个,但是要注意,在下面这种情况下不能共存

即其中1的构造函数有默认实参且默认实参和2相同,就不能有2这个构造函数。因为在创建对象时,如果没有实参,编译器不知道要调用哪个构造函数。

构造函数放在private的情况

​ 单例模式

​ 使用

6.常量成员函数

不改变数据的函数

如果不加const,在下面这种情况会报错

7.参数传递(值传递和引用传递)

引用传递的速度和指针传递差不多,效率一般比值传递高,所以尽量传引用。如果不希望对方改,可以加const

operator表示操作符重载,operator += 表示重载+=操作符

8.返回值传递(值传递和引用传递)

尽量采用引用传递

9.友元(friend)

image-20240106214841223

友元函数可以自由取得private的成员变量

友元打破了封装

相同class的各个对象互为友元

10.什么时候可以使用引用传递,什么时候可以返回引用

如果return的变量是在函数内部创建的,就不能使用引用返回,因为局部变量在函数结束时会自动销毁。

11.操作符重载(成员函数,有this)

this不一定要显式写出。

返回引用时,传递者无需知道接收者是以引用形式接收,return时把值return出去。如果是指针,要把指针指向的object return出去

上面的operator +=返回值可以是void吗?如果是void会有问题,比如使用下面这个连续+=的时候会出错

12.操作符重载(非成员函数,无this)

全局函数,不能return引用,因为返回的是在函数内部创建的

<<不能使用成员函数的方法重载,因为<<作用于cout,而cout是多年前就已经写好的,是ostream类型

13.临时对象 typename();

如上图的complex(real(x)+y, imag(x));创建了complex的临时对象

黄色部分为临时对象

14.拷贝构造、拷贝赋值、析构函数

有指针成员的类一定要有拷贝构造和拷贝赋值

一定要在拷贝赋值中检查是否自我赋值

七、堆、栈与内存管理

1.栈(stack)

stack是存在于某作用域的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址,在函数本体内声明的非static变量,其所使用的内存块都取自stack

栈中对象的生命周期:离开作用域就会自动销毁。

2.堆(heap)

heap是指由操作系统提供的一块全局内存空间,程序可动态分配从其中获得若干区块。

常见的动态分配堆内存的方式,malloc、new

从堆中申请的内存要手动释放

堆中对象的生命周期:如果用new申请的,在delete之后结束,如果是用malloc申请的,free之后结束

使用new时,编译器的执行步骤是先分配内存,再调用构造函数

使用delete时,先调用析构函数,再释放内存

在VC中动态分配内存时具体得到的内存块

除了Complex对象所占的8个字节之外,还额外分配了一些内存,红色部分是cookie(记录整块的大小),灰色部分是调试模式下分配的内存块,每格4个字节。因为在VC下分配的内存必须是16的倍数,所以增加了三个pad填充(青色部分),最后分配的内存大小是52+12=64字节。

64的16进制表示为40,cookie是41,其中1表示的是这块内存操作系统分配出去了

在非调试模式下

VC中动态分配所得的数组内存分配情况

灰色部分是Complex,除了上面介绍的外,还会增加一个内存块还记录数组的元素个数

array new一定要用array delete

八、静态(static)

静态函数没有this

静态数据成员要在类外部定义,非静态数据成员不能在类外部定义。

单例模式的设计

这种方式不允许外界来创建对象,但是把static A a放在声明里有个缺点是如果没用到这个类也会占用内存

更优的方式

九、函数模板template

和类模板不同在于函数模板不用显式的指出类型,编译器会对函数模板进行自动推导

十、命名空间namespace

可以防止重名,将所有东西放在一个namespace里面

命名空间的使用,三种方式

十一、类与类之间的关系(Composition、Delegation、Inheritance)

1.Composition(组合),表示has-a

类和类之间的关系,一个类里面有另一个类的变量

适配模式Adapter

Composition关系下的构造和析构

构造由内而外

Container的构造函数首先调用Component的default构造函数,然后才执行自己

析构由外而内

Container的析构函数首先执行自己的析构函数,然后才调用Component的析构函数

2.Delegation(委托)

通过指针连接,和Composition的区别在于委托是通过指针连接,连接的两个类生命周期不一样

可以使用引用计数

3.Inheritance(继承),表示is-a

is-a:是一种,比如小轿车是一种车

继承关系下的构造和析构

父类的构造函数必须是virtual函数(虚函数)

构造由内而外

子类的构造函数首先调用父类的default构造函数,然后才执行自己

析构由外而内

子类的析构函数首先执行,再调用父类的析构函数

4.Inheritance + Composition

继承和组合关系下的构造和析构

第一种情况

构造函数调用顺序:Base-Component-Derived

析构函数调用顺序:Derived-Component-Base

第二种情况

构造函数调用顺序:Component-Base-Derived

析构函数调用顺序:Derived-Base-Component

5.Delegation + Inheritance

功能最强大的组合。

可以解决的问题

一份数据,多种不同呈现方式的场景,改变数据时,所有相关呈现方式的内容也跟着变

设计模式之观察者模式

设计模式之组合模式

设计模式之原型模式

创建未来的class

十四、虚函数

非虚函数:不希望子类重新定义(override,覆写)它

虚函数:希望子类重新定义(override,覆写)它, 它已有默认定义

纯虚函数(pure virtual):希望子类一定要重新定义(override,覆写)它(子类不重新定义会报错),它一般没有默认定义(但可以有默认定义)

设计模式之模版模式

父类方法中一些函数内部调用函数的实现由子类来实现


C++面向对象
http://cxspace.org.cn/2024/01/07/C++面向对象/
Author
陈晓
Posted on
January 7, 2024
Licensed under