cpp

C++ :面向对象
面向对象特征:封装——继承——多态——异常

 


特性:
1.c++完全兼容C
2.c++标准:c++11标准 c++14标准
3.c++应用:游戏引擎 服务器开发 UI-QT
4.c++ main: int
5.c++头文件不用.h : include <cmath> == include <math.h>

 


1. <C++扩展-命名空间>
——————————————————————————————————————————————————————————————————————————————————————————
#include <iostream>
1 using namespace std;
2 using std::变量;
3 std::变量;

cout cin

声明定义命名空间:
namespace spaceA{
int a = 10;
namespace spaceB{
struct std{
int age ;
}
}
}

使用:
using namespace spaceA;
using spaceA::a;
spaceA::a;

1. namespace 既是定义也是声明。
2. :: 域作用操作符。
3. namespace 命名空间只能全局范围定义。
4. namespace 命名空间可以嵌套定义。
5. namespace 命名空间声明与实现分开。
6. namespace 命名空间取别名 namespace name_new = name_old;
7. namespace 实现了多人协同独立开发。

 

2. <C++扩展-引用>
——————————————————————————————————————————————————————————————————————————————————————————
int a = 1;
int &p = a; == int *const p = a; //p只读,且针对地址

1. 引用:已存在的!变量的别名! 替代指针!
2. 本质:常量指针
3. 引用:定义同时必须初始化,且之后不能改变(否则为赋值)!
4. &前有类型为引用,其他皆为取址
5. 可对引用再次引用,多次引用只是取别名
6. 引用于变量地址相同
7. c++编译器,在编译过程中使用常指针作为引用的内部实现,且引用空间大小和指针相同:32:4byte 64:8byte!
8. 结构体中的引用不能进行初始化。
9. 引用作为函参不进行初始化。
10.函数不能够返回函数里面栈上的局部变量的对象引用!


引用于函数:
int& fun(int& a);
1.做函参(不需要拷贝动作)

2.做返值(不需要拷贝动作)
可以作为左值!因为返回值为引用(别名)

引用于结构:(不需要值拷贝)
struct info{
int age;
};
const info& fun(info &a);


引用于指针:(用于简化指针)

引用类对象:

引用于const:
const int a = 1;
const int& aa = a;

int b = 1;
const int& bb = b;

 

3. <C++增强-函数>
——————————————————————————————————————————————————————————————————————————————————————————
1.内联函数: inline + FUN
本质:牺牲代码段空间为代价,提高运行的时间效率;
特点:编译器在编译阶段进行展开;不需要压栈出栈,
c++编译器将函数体直接插入到被调用的地方;
没有普通函数调用时的开销(压栈,跳转,返回)
弊端:函数不能庞大,循环,判断,取址

1.内联函数的声明和实现必须放在一起
2.内联函数宏定义实现函数功能
3.内联函数在最终生成的代码中是没有定义的,编译器最终会展开。
4.在哪个文件使用,就在哪个文件中定义
5.内联函数inline只是对编译器的请求,请求失败:
1.存在任何形式得循环语句
2.存在过多条件判断语句
3.函数体过于庞大
4.对函数进行取址操作
6.内联函数没有内存地址
7.函数:不断的压栈出栈,再压栈出栈,开销比较大。(解决宏的弊端!)
8.宏:预处理阶段展开;不需要压栈出栈,弊端数据类型不处理,不安全。
2.默认参数:定义函数参数直接赋值,默认起始从右至左! void fun(int a=10, int b=20);
1.没有实参则为默认值。
2.函参从左到右,如果一个参数设置默认值,则后面必须被设置默认参数。
3.函数声明与定义:声明写默认参数,定义不写默认参数。
4.占位参数:没什么意义!
3.重载函数:函名相同,函参不同,缺一不可!
1.本质: 编译器处理是相互独立的不同函数。
编译器: void fun(char a,int b) --> fun_ci
void fun(char a,int b, int c) --> fun_cii
g++编译器比gcc编译器更强
2.g++编译c也可以实现函数重载,重载只与编译器有关
3.函数指针指向成员函数存在this指针不成功,静态成员函数没有this指针!

注意:函数重载,不要写默认参数,避免函数冲突!
类型严格匹配,不匹配隐式转换,字符串不行!

与函数指针:typedef int(fun)(int,int); fun *pfun;
typedef int(*fun)(int,int); fun pfun;
int (*fun)(int,int)=NULL;
函数指针通过形参匹配函数重载(指向匹配函数),自身不能重载!

 

3. <C++增强-Const>
——————————————————————————————————————————————————————————————————————————————————————————
1.const 只读!
1.const + 成员变量 :只读,只能在初始化成员列表时被赋值!
2.const + 成员函数 :他不能改变对象的成员函数!不会改变类中的成员变量
int get()const //作用:增强代码可读性,表示只读函数
{
}
3.const 不能修饰全局/普通函数
4.const + 对象:
Class A{

}
const A a; //a里面所有东西都不能改!跟访问权限无关
1.const 修饰的对象为常量对象,任何成员都不能修改!
2.const 修饰的对象只能访问类中const函数!:因为被const修饰的这些函数不能被改变!其他函数有被改变的风险!
3.const 修饰的对象可以访问public成员变量!
5.const + 引用: 防止改变被引用对象的内容!
1.不能改变引用对象!
2.

6.const + 指针
1.
7.const + 函数返回值:
1.用的不多,作用:不希望返回值以后被调用者给改变!
const int fun()
{
int a =100;
return a; //无意义
}
const *int fun()
{
int *a =100;
return a;
}
const int p = fun();

1.const修饰变量,一般都给变量初始化一个值!
2.例子:
const int * a
int* const a
const int* const a
3.函数调用结,将返回值保存到临时内存,再释放函数内存,再拷贝至返回变量

 

const int a == int const a (const编译器阶段,宏是在预处理阶段)必须初始化
(C语言:a可以通过指针改变,所以a为假常量,如:int arr[a] 报错!)
(C++:a不可以通过指针改变,所以a为真常量,如:int arr[a],正常!)

 


3. <C++增强-C/C++混合>
——————————————————————————————————————————————————————————————————————————————————————————
1. c/c++混合编程
extern "c"
{
//告诉g++编译器用c语言规则去编译!
extern int fun(void);
或者直接包含头文件
2.h
}

1.c int fun(void){};
1.h extern int fun(void);
-->gcc 1.o

2.cpp
2.h
-->g++ 2.o

g++ 1.o 2.o //error

1.g++编译器和gcc编译器生成的符号表不一样 1.o 2.o
解决方法:
2.extern外部声明

1.h:
{
#ifndef __1_h__
#define __1_h__

#ifdef __cplusplus //如果是g++编译器,通过#endif告诉g++编译器,用c语言规则去编译!
extern "c" //这个只解决了g++的问题! 但gcc编译器不认识这个!
{
#endif

extern int fun(void);

#ifdef __cplusplus
}
#endif
}
1.g++编译器可以编译c文件,但要通过 extern "c" 告诉g++编译器,用c语言规则去编译!不能用c++的标准去编译!
2.A用户有c++调用B用户的c库,如果b没有
A用户 : gcc 编译器 lib.so
B用户 : g++ 编译器 链接 lib.so
这个时候就需要再A用户的头文件,表明 extern "c"!


3. <C++增强-NULL/nullptr>
——————————————————————————————————————————————————————————————————————————————————————————
c : NULL = (void*)0
c++: NULL = 0
c++: nullptr = (void*)0

1.void fun(int *p)
c: fun(NULL) 正确
c++: fun(NULL) 错误: 因为c++中NULL为整数0! 且与重载函数void fun(int p)冲突!

1.int *p =0; 编译器编译是自动转换 int *p =NULL;
2.c++空指针用nullptr!

4. <C++扩展-类与对象>
——————————————————————————————————————————————————————————————————————————————————————————
1.类: 属性-成员变量
方法-成员函数
1. 一般类名的首字母大写。
2. 封装:对外开放数据,屏蔽数据,提供接口(将属性进行方法封装,进行访问控制)
3. class 默认所有成员为 private
4. 类的定义与实例化{
类定义:
class Data{
public:
void init(void);
private:
int year;
int mon;
protected:
int day;
}
实例化:
Date one;
one.init();
}
5. 类和结构体相似,都是数据类型,在定义是不分配内存,实例化对象时才分配栈空间。
6. 访问控制符:类内外部访问:public
类只内部访问:protected private
7. 命名空间和类的声明与实现分离
8. 类的内部,外部为实例化对象
9. 属性一般为私有,接口有公有且加限制条件
10.封装:就是把属性和方法放在一个类里面,并添加访问权限保障安全。


#类的get方法:int getter(){ return a ;}
#类的set方法:void setter(int new){ a = new ; }
1.类加上一个增强版的结构体


2.构造:在类中定义且类名相同! (定义立刻初始化,避免危险!)目的:对对象进行初始化,系统自动调用
1.就是为了对象数据的初始化,因为类的成员不能在声明中进行初始化!
2.类肯定不会占用内存空间,实例化才会分配内存空间。
3.自定义构造函数则需要为public
4.有参构造函数:
1.更方便对对象进行初始化!
2.构造函数也可以重载!如过重载了构造函数,需写默认构造函数,否则系统不会再创造。
3.class A{
public:
A(int a, int b, int c)
{
aa =a;
bb =b;
cc =c;
}
A(int a, int b)
{
aa =a;
bb =b;
}
private:
int aa;
int bb;
int cc;
int &p;
const int q;
}
A a_1(1,2,3);
A a_2(1,2);
4.没有手动实现任何构造函数时候,编译器会自动生成一个默认的构造函数,一般c++编译器自动调用
5.初始化成员列表:
1.前面带冒号,后面逗号分隔!
2.成员名称+初始化值
3.c++提供了一种方法,通过初始化成员列表对成员进行初始化,而不是赋值操作!
A(int a, int b, int c):a(1),b(2),c(3)
{
}
4.初始化成员列表,先于构造函数;列表完成再进入函数;在嵌套类外层的构造函数,进行初始化列表
6.类中存在引用,在构造函数中必须对引用进行初始化!且必须使用初始化成员列表!且所有重载构造函数都要初始化!
A(int a, int b, int c):p(a) //这是初始化!
{
p = a; //错!这是赋值不是初始化!此时引用已经被定义,且还没有被初始化就被赋值!
}
7.类中存在Const,在构造函数中必须对Const进行初始化!且必须使用初始化成员列表!且所有重载构造函数都要初始化!
A(int a, int b, int c):q(100) //这是初始化!
{
q = a; //错!这是赋值不是初始化!const不能赋值
}
8.类中定义类,会调用类中的默认构造函数,但是如果没有默认构造函数则会报错!
可通过初始化成员列表传参,调用他的重载构造函数,绕过无参构造函数!且所有重载构造函数都要初始化!(绕过无参构造函数的调用)


无参:
拷贝:深拷贝 浅拷贝

拷贝:class 类名{
类名(const 类名 & another){ //const保护被拷贝值!
拷贝结构体
}
}
类名 t ;
类名 t1(t); //t给t1拷贝初始化 (类没有定义,系统默认提供,两个一模一样!)
== 类名 t2 = t ; //属于构造拷贝(无参初始化)
!== 类名 t3 ;t3 = t ; // 没有在初始化进行,不是拷贝属于赋值

默认:
默认无参构造函数:当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空

默认拷贝构造函数:当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制

深拷贝:
浅拷贝:

初始化列表:
重点:
1.无返无参,类名相同,可重载,可默认参,一经实现,默认不复存在
2.对象创建时自动调用,完成初始化工作!
3.如果类中自定义构造函数,对象定义必须初始化! 否则系统不会默认再次创建构造函数!
4.显示提供构造函数,默认构造不存在;不提供默认构造自动调用,什么都不干!
5.显示拷贝构造不存在时!系统提供默认拷贝构造函数,将拷贝值一模一样复制;(如:拷贝:)
6.没有任何显示构造函数(显示无参,有参,拷贝)时,系统默认构造才出现
7.当自定义构造函数时,定义对象的同时必须进行初始化!!!
8.构造对象成员的顺序跟初始化列表无关,跟定义顺序有关
9.对象的初始化只能通过构造函数!
10.拷贝构造函数本质是构造函数
11.无参构造 有参构造 拷贝构造 析构函数


3.析构:~类名();
1.对象销毁时调用析构函数!
2.无参无返,不能重载!
3.当类中没有定义析构函数时,编译器默认提供一个无参析构函数,并且其函数体为空
4.每个类只能有一个析构
5.有垃圾写析构,无垃圾不用写,只是系统提供的接口!
6.析构调用与构造顺序相反!谁先构造,谁后析构!(栈原理:先进后出,压栈弹栈)
7.如果自定义析构,默认析构则不存在!
8.构造new,析构delete
9.对构造函数进行清理
10.如果在构造函数进行了malooc,则需要在析构函数进行free!防止内存泄露!
11.字符串通过指针代替数组可以节省内存空间!
12.栈空间回收之前调析构回收堆空间


4.多个对象的构造/析构:
1.外部类-内部类
先构造父类,再构造成员对象,最后构造自己!(成员对象按顺序构造!)
析构相反!

5.多个对象的赋值:
1.已实例化对象去初始化定义对象!
2.他们类中的指针变量,就会指向同一块内存空间! 危害:两个类的指针变量指向同一块内存空间,析构会冲突释放两次!一个销毁另一个也会出错!(指针直接赋值,没有重新进行分配空间和再次赋值)
3.可以定义后再进行成员赋值,这样指针的空间不一样。 问题:复杂!繁琐!

----> 引出拷贝构造函数:
1.当使用已经实例化对象,去初始化新的对象时,他不会去调用默认构造函数和重载构造函数! 而是系统会默认再创造一个拷贝构造函数并调用!
Test(const Test &t)
Test t2 = t1 //调用拷贝构造函数 t2.Test(t1) :引用右值,操作左值
2.如果函数的形参是一个对象不是引用,那么就会再产生一个对象!循环产生!
3.const 修饰引用 = 只读 : 作用是保护引用对象!

----> 浅拷贝/深拷贝
浅拷贝:默认拷贝构造函数--危险! :对指针变量进行粗暴地赋值操作!
深拷贝:手动实现拷贝构造函数--安全 :对指针变量进行重新内存申请,再进行赋值!
区别用途:
类里面无指针,用浅拷贝!
类里面有指针,用深拷贝!

4.如果函数参数使用对象而不是引用,就会默认调用拷贝构造函数,因为实参传给形参相当于进行了一次赋值! 且降低运行效率!有临时对象产生!
void func(Test t) < void func(const Test &t) :函数参数使用引用代码效率最高!

6.编译器对类的属性方法的处理机制:
实例化对象,内存占用和分布:
class Data{
public:
void init(void); //不占内存
private:
int year; //占内存
int mon; //占内存
} = 2 * 4 = 8byte !
1.c++类的属性和普通方法:
进程内存四区(代码区+全局区+堆+栈):属性在栈上,方法在代码段(代码区是共享的只读的) :单片机代码区在rom,linux的代码区在ram(由rom加载到ram)
2.实例化对象,编译器默认会生成三个函数: 默认构造函数 默认拷贝构造函数 析构函数
3.类里面的成员函数是可以被所有相同类实例化的对象访问到,是共享的!
4.
---->
this指针:
编译器在编译时候自动在函数参数里面添加
class Data{
public:
void init(int a){return value;}; => 编译器 void init(Data *const this, int a){return this->value};
//this指针指向实例化对象
private:
int year;
int mon;
}
Data Data_1;
Data_1.init(1); => Data_1.init(&Data_1,1); //this指针就指向调用函数的对象,这样就区分了哪个对象在调用函数!
1.函数都在代码段,且共享!类的实例化对象通过this指针找到对应对象的地址变量!
2.c++类里面的函数,编译器都会隐藏一个this指针,且是第一个形参!
3.this指针用法二:
class Data{
public:
int a,b;
void init(Data *const this, int a, int b)
{
this a = a;
this b = b;
};
}
4.每一个被实例化的对象,编译器都会分配一个this指针

7.静态成员变量/函数
1.c++中全局变量太多不好维护!
2.和类有关系,和对象无关!
3.静态成员变量:实现了同类对象的共享机制!
4.静态成员局部于类!不是类或者对象的成员变量,只是作用于在类和他的实例化对象。:看一下类的大小就知道!不包括静态成员!
5. 实例化:
class Data{
static int a; //只是声明
}
//定义类中的静态成员变量
int Data::a = 0; //如果不初始化,默认为0
Data data;
访问:1.类 :cout << Data::a <<endl //最好
2.对象:cout << data.a <<endl
6.类中静态成员函数,只能访问类中的静态成员变量,不能访问非静态成员变量! 原因: 静态成员函数其实不是类的函数!只是仅作用于类。 重点:不是类的成员函数,编译器没有提供this指针!
class Data{
static int a; //只是声明
static void fun(void){a =0;}
}
Data data;
访问:1.类 : Data::fun(); //最好
2.对象: data.fun();
7.单例模式:一个类只能实例化一个对象!
8.有差异的构建对象,无差别的直接用类! 省了开销
class math{
static sin();
static cos();
};
math::sin;
math::cos;


<C++扩展-继承派生>
——————————————————————————————————————————————————————————————————————————————————————————

类与类关系:has-A uses-A is-A
has-A:包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类
uses-A:一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现
is-A:机制称为“继承”。关系具有传递性,不具有对称性。 

class 派生类名:[继承方式] 基类名{
派生类成员声明;
};


继承方式:公有继承,保护继承,私有继承
派生类继承:
公有继承:当公有继承时,基类的公有变成派生类的公有,基类的保护变成派生类的保护,基类的私有变成派生类的私有。
保护继承:当保护继承时,基类的公有变成派生类的保护,基类的保护变成派生类的保护,基类的私有变成派生类的私有。
私有继承:当私有继承时,基类的公有变成派生类的私有,基类的保护变成派生类的私有,基类的私有变成派生类的私有。
派生类访问:
继承公有:在派生类内可以访问,在派生类外可以访问!
继承保护:在派生类内可以访问,在派生类外不能访问!
继承私有:在派生类内不能访问,在派生类外不能访问!

-----> 构造和析构:
1.先构造基类,再构造成员对象,最后构造自己!(成员对象按顺序构造!)
析构相反!
2.如何在派生类的对象中,指定调用基类的有参构造函数? 目的:在派生类中,对基类的属性进行初始化!
方法:在派生类的构造函数中,使用初始化成员列表,指定调用基类的某个构造函数!
3.派生类的成员变量 和 基类的成员变量 冲突怎么办?
方法:
class A:{
public:
int p;
};

class B: public A{
public:
int p;
int q = A::p; //类内访问
};
B b; //类内访问
b.p //访问到B中的P
b.A::p //访问到A中的P

1.尽量不要很基类的变量名一样!
4.派生类的成员函数 和 基类的成员函数 (函数名和参数都相同) 冲突怎么办?
方法:
class A:{
public:
void fun(voud);
};

class B: public A{
public:
void fun(voud);
int q = A::fun(); //类内访问
};
B b; //类内访问
b.fun(); //访问到B中的fun
b.A::fun(); //访问到A中的fun

1.函数名相同和参数相同:被隐藏
2.函数名相同和参数不同:也被隐藏
3.他们处理方法相同! ::

2.多继承:派生类继承多个基类(继承多父类特性)
语法:class <派生类名>: <继承方式1><基类名1> , <继承方式2><基类名2> , ...{
<派生类类体>;
}

1.鱼与熊掌兼得的做法!
3.环继承:A -> B:A - C:A -> D:B:C
class A:{
public:
int a;
};
class B: public A{
public:
int b;
};
class C: public A{
public:
int c;
};
class D: public B, publicC{
public:
int d;
};
D d;
1.环形继承大小:
D = B + C
2.环形继承访问:
1.d.A::a; //没意义,共同属性只需要一份就行!
2.d.a; 出错: 模棱两可!
------>
4.虚继承: virtual
动物
鸟类 哺乳
蝙蝠

鸟类虚继承动物
哺乳虚继承动物
蝙蝠中只有一份!
作用:
1.减小内存大小!
2.访问属性方法更方便! d.a -> OK
3.虚继承解决了环形继承共同属性和方法问题!
4.多继承现实使用少!不容易维护!

5.静态成员的继承:
1.派生类可以继承基类的静态成员变量和函数,且共享!
2.基类和派生类静态成员相同,怎么办?
派生类隐藏基类的静态成员!处理方式和之前一样。


1.特点:C++代码重用性是通过继承这一机制来实现,提高执行时间的效果!
2.基类/父类 --> +new -->派生类/子类
3.基类:人; 派生类:教师,学生
4.派生类的大小也继承了基类大小! :注意字节对齐
5.保护继承 和 私有继承,目的是为了下一个派生是否可以访问祖父的类!: 如果不想让后面的派生类访问我的继承,用保护继承!
6.派生类继承了不可以访问,但是已经继承了。

虚继承:
1.如果一个派生类从多个基类派生,而这些基类又有一个共同的基类,则在对该基类中声明的名字进行访问时,可能产生二义性
2.如果在多条继承路径上有一个公共的基类,那么在继承路径的某处汇合点,这个公共基类就会在派生类的对象中产生多个基类子对象
3.要使这个公共基类在派生类中只产生一个子对象,必须对这个基类声明为虚继承,使这个基类成为虚基类。
4.虚继承声明使用关键字virtual

 

继承与构造析构:
在子类对象构造时,需要调用父类构造函数对其继承得来的成员进行初始化
在子类对象析构时,需要调用父类析构函数对其继承得来的成员进行清理

继承中同名成员变量处理方法:
#同名成员变量和成员函数通过作用域分辨符进行区分
父类和子类成员变量成员函数同名,但存储位置不同,子类依然继承父类
通过作用域分辨符::区分

1.子类对象在创建时会首先调用父类的构造函数
2.父类构造函数执行结束后,执行子类的构造函数
3.当父类的构造函数有参数时,需要在子类的初始化列表中显示调用
4.析构函数调用的先后顺序与构造函数相反
5.原则:先构造父类,再构造成员变量,最后构造自己
先析构自己,在析构成员变量,最后析构父类


继承中关键字static:
派生类中访问静态成员: 类名 :: 成员
通过对象访问: 对象名. 成员
1.基类定义的静态成员,将被所有派生类共享

 

重点:
1.一个类可以派生自多个类,可以从多个基类继承数据和函数
2.is-a 关系:例如,哺乳动物是动物,狗是哺乳动物,则:狗是动物!
3.派生类可以访问基类中所有的非私有成员(即:public protected)
4.派生类继承基类所有方法,除去:构造,拷贝,析构,友元函数和重载运算符!!

 

<C++扩展-多态>
——————————————————————————————————————————————————————————————————————————————————————————
多态:意味着调用成员函数时,会根据调用函数对象的类型来执行不同的函数
继承 + 虚函数重写 + 父类指针(父类引用)指向子类对象

advance(英雄,系数)
advance(基类引用,基类的指针,系数)
同一个接口,传入不同的实例化对象,原因是他们拥有共同的基类
一个函数有多种形态
英雄回城:各个英雄形态不同!

分类:
1.静态联编/多态:编译完知道程序调用哪个函数 : 函数重载 隐藏 运算符重载 泛型编程
2.动态联编/多态:编译完不知道程序调用哪个函数 :

1.联编:将模块或者函数合并在一起生成可执行代码的处理过程,并给每个内部或者外部的模块或者函数分配内存地址,

1.Cpp与C相同,是静态编译型语言
3.联编是指一个程序模块、代码之间互相关联的过程
4.静态联编:程序的匹配、连接在编译阶段实现,也称为早期匹配。重载函数使用静态联编
5.动态联编:程序联编推迟到运行时进行,所以又称为晚期联编(迟绑定)switch语句和if语句是动态联编的例子

 

重载:所谓重载,就是重新赋予新的含义

函数类型 函数名 operator 运算符名称(形参表列){
重载实体;
}


1.运算符重载: 对运算符赋予添加其他功能,本质:运算符重载的本质是函数重载
操作符重载:
关系重载:>, <, ==, >=, <=, !=
逻辑重载:|| && !
赋值重载:=
单目重载:+ - * &
双目重载:+ - * / %
位运算重载:& | ~ ^ << >>
自增减重载:++ --
申请释放重载:new new[] delete delete[]
输入输出重载:
其他运算重载:()函数调用 ->成员访问 ,逗号 []下标
不可重载:
成员访问:.
成员指针访问:.* ->*
长度:sizeof
条件:?:
域运算:::
预处理: #
1. 运算符重载,由做操作数调用,有操作数做实参 :t1+t3 -> t1.operator(t3)
1.+运算符重载: Test operator + (const Test &t){}
重点: Test t3 = t1 + t3 //1.触发+运算符重载函数 和 触发拷贝构造函数? 错!:会一次运算符重载函数和两次拷贝构造函数,编译器优化了拷贝构造函数
先构造t3,创建了默认构造函数
2.>运算符重载: bool operator > (const A &t){}
class A:{
public:
int a;
bool operator > (const A &t)
{
if(this.a > t.a)
return 1;
else
return 0;
}
};
A m;
A n;
int rel = m>n; //会自动调用 operator> 函数并返回bool类型值
3.[]运算符重载: char operator [] (int index){} //返回数组下标数的字符
class A:{
public:
char ptr[10];
char operator [] (int index)
{
if(index < 0 || index >= strlen(ptr))
return '\0'
return ptr[index];
}
};
4.=运算符重载: char operator = (int index){}
5.++运算符重载:
前置++ :Test& operator ++ (){ return *this}
后置++ :Test operator ++ (int){}
问题:前置++和后置++哪个效率更高? 前置更高!
5.<<输出运算符重载: operator << (){} //形参是什么 返回值是什么
如果左操作数不是对象,将左操作数和右操作数当做实参传给函数


1.如果函数返回值是对象,就会有临时对象的产生!除非有编译器的优化!可以返回引用解决
2.c++会自动实现一个运算符重载函数,但是是浅拷贝!
Test t1; //调用默认构造函数
Test t1(10);//调用带参构造函数
t1 = t2; //调用等于重载函数
Test t2 =t1;//调用拷贝构造函数
2.友元重载:同类对象间无私处,异类对象间有友元!
friend 类型 函数名(形参) //只有朋友才能访问隐私
友元类:
友元函数:
友元成员函数:可以直接访问类中的私有成员的非成员函数!


1.在类中将函数声明为类的友元函数,就可以访问类的私有成员!
2.友元函数声明可以放在私有也可放在公有也可在保护!
3.元友函数没有this指针:他是普通函数,不是类的成员函数,只是声明类的友元函数
4.类A的成员函数想访问类B的成员函数,将类类A的成员函数声明为类B的友元函数。 == 将类A声明为类B的友元类
5.友元关系不能继承!(函数 类都不能继承): 你爸爸的朋友是你的朋友吗? 不是!
6.友元作用提高了程序的运行效率,但它破坏了类的封装性和隐藏性,使得非成员函数可以访问类的私有成员!
7.不到万不得已,不提倡用友元! pthy和java已经剔除了友元!

 

诠释:类的机制后实现了数据的隐藏与封装,类的数据成员一般定义为私有成员,成员函数一般定义为公有
的,依此提供类与外界间的通信接口。但是,有时需要定义一些函数,这些函数不是类的一部分,但又
需要频繁地访问类的数据成员,这时可以将这些函数定义为该函数的友元函数。除了友元函数外,还
有友元类,两者统称为友元。

作用:提高了程序的运行效率,但它破坏了类的封装性和隐藏性,使得非成员函
数可以访问类的私有成员。


友元函数:可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元
的名称前加上关键字 friend

friend 类型 函数名(形参) //一个函数可以是多个类的友元函数,只需要在各个类中分别声明。

全局函数作友元函数;
类成员函数作友元函数

1.全局函数作为友元函数
2.成员函数作为友元函数


友元类 :友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息

#当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类

friend class 类名;(类名必须是程序中已定义的类)

注意:
1.友元关系不能被继承
2.友元关系是单向的,不具有交换性(若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明)
3.友元关系不具有传递性(若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的声明。)

 


3.成员重载:

 

重载规则:
1.Cpp不允许用户自己定义新的运算符,只能对已有的Cpp运算符进行重载。
2.Cpp允许重载的运算符 如图:
3.重载不能改变运算符运算对象(即操作数)的个数
4.重载不能改变运算符的优先级别。
5.重载不能改变运算符的结合性
6.重载运算符的函数不能有默认的参数
7.重载的运算符必须和用户定义的自定义类型的对象一起使用,其参数至少应有1个是类对象(或类对象的引用)
8.用于类对象的运算符一般必须重载,但有两个例外,运算符=和运算符&不必用户重载
9.应当使重载运算符的功能类似于该运算符作用于标准类型数据时候时所实现的功能
10.运算符重载函数可以是类的成员函数,也可以是类的友元函数,还可以是既非类的成员函数也不是友元函数的普通函数

 


泛型编程:

 

动态多态/联编:
条件:
1.有继承关系!
2.有虚函数!
3.有基类指针指向派生类对象,或者基类的引用变量引用了派生类对象!
应用:没个具体行为的多态
虚函数:
virtual 函数类型 函数名(形参)
作用:实现了多态机制! 基类定义虚函数,派生类可以重写该函数!
虚函数重写条件:
1.基类和派生类之间!
2.被重写的函数是个虚函数!
3.派生类的函数和基类的虚函数名,形参,返回值相同!
class base
{
public:
void fun(void){}
virtual void fun1(void){}
}

class A : public base
{
public:
void fun(void){} //隐藏了基类的fun
void fun1(void){} //对基类的虚函数进行了重写
}

A a;
a.fun(); //隐藏了基类的fun,调用了派生类的函数 :隐藏
a.fun1(); //对基类的虚函数进行了重写,调用了派生类的函数 :从写
//通过定义基类指针,再指向派生类!
base* p;
p = &a;
//应用:
p -> fun //调用了基类的函数
p -> fun1 //调用了派生类的函数
总结:总过基类的虚函数,实现多态!


1.特殊的成员函数,实现了多态的原理!c++学习的重要标准!
2.构造函数不能是虚函数!
3.对基类虚函数的重写,也叫覆盖,也叫override
4.重点:基类的函数如果是虚函数,通过基类指针指向派生类时候,可以通过虚函数调用到派生类中的重写函数!
5.通过派生类没有函数接口,则调用基类的虚函数!
6.通过基类调用非虚函数,永远也调用不到派生类的函数,只能调用的基类的普通成员函数!
7.基类的虚函数可以在派生类中被从写/覆盖/override!!!
8.从写是发生在不同的作用域,基类虚函数被从写了。
9.从写:有基类虚函数的名字一样!参数一样!返回值一样! 三个一样!
10.子类从写父类的虚函数。
11.动态多态重点:基类的指针/引用!:虚函数调用派生类重写的函数,普通函数调用基类的函数

虚函数原理:
虚函数指针(virtual function pointer :vptr):
1.只有拥有虚函数的类才会拥有虚函数指针,每一个虚函数都会对应一个虚函数指针!
2.虚函数指针一定是存在类中,而不是存在对象中!
3.编译器在编译类时候,就会生成虚函数指针,放在类中!
4.虚函数指针对于类的所有对象都是共享的!
5.虚函数指针在内存的数据段里面的只读数据段上!所以他不占对象的内存空间!
虚函数表(virtual function table :vtbl):
1.每个类都有一个虚函数表:表里是类的虚函数指针
2.继承基类虚函数的派生类,也有一个虚函数表,且他和基类的虚函数表一样!
3.虚函数表也是在只读数据段上!
4.window和linux系统下,虚函数表会多1: 虚函数 = 虚函数个数 + 1
5.继承基类虚函数的派生类,派生类中还有自己独有的虚函数,则虚函数之和。
6.派生类中的函数与基类的虚函数的函数名相同,参数相同,返回值相同,不管有没有virtual,都是虚函数!
7.派生类中的函数与基类的虚函数的函数名相同,参数相同,返回值相同,派生类的虚函数指针,重写/覆盖了基类的虚函数指针。
8.派生类中的虚函数表,先存自己的虚函数指针,再存基类的虚函数指针!
虚函数表指针(虚表指针:vfptr):
1.虚表指针在对象里面,前提对象中有虚函数!且存放在对象的首地址!:目的为了保证运行的快捷性!
2.虚函数表属于类!不属于对象!
3.对象中通过虚函数表指针,访问到类中的虚函数表类!
4.所有对象访问虚函数,都是通过虚表指针进行访问!

动态绑定:基类指针指向派生类对象!!!
1.如果有基类指针指向派生类对象,并通过基类指针调用某些函数时,编译器是如何处理的?
1.编译器先检查调用的函数是否为虚函数,如果不是虚函数采用静态联编,直接调用基类中函数,不论派生类是否隐藏了基类中的函数
2.如果函数是虚函数,会访问被指向派生类对象中的虚函数表指针,通过该虚表指针就找到了派生类的虚函数表,在虚函数表中去查找调用该函数的虚函数指针!
如果派生类重写/覆盖了基类的虚函数,派生类的虚函数表中保存的是派生类的函数指针,因此,通过基类指针调用函数最终调用的是派生类的函数!
如果派生类没有重写/覆盖了基类的虚函数,派生类的虚函数表中保存的是基类的函数指针,最终调用的是基类的函数!
如果函数是虚函数,编译时是不知道该函数的调用到底该调用那个函数,在程序运行的时候才知道!
3.基类指针调用函数,不要妄想调用到派生类中独有的函数!!!

纯虚函数:没有实际定义的虚函数就是纯虚函数!:就是函数里面什么都没有!
1.如果基类的虚函数,在每一个派生类中都被重写了! 似乎基类的虚函数根本就没有必要实现! 那么就定义为纯虚函数
virtual void fun1(void) = 0; //只是声明没有实现,在类中的虚函数表中填充为0进行占位,派生类会将0地址的虚函数地址进行重写!
告诉编译器,为纯虚函数,没有函数体!
2.纯虚函数在基类中值进行了声明,并没有实现!要在派生类中重新实现!
3.纯虚函数在派生类中重写基类的纯虚函数进行声明!


抽象类: 类里面只要有一个纯虚函数!
1.抽象类不能实例化对象!不知道具体动作,因为里面都是模糊的,没有意义!
抽象类通过派生类去实例化对象!
抽象类只是规定了每一种类的特征特点!
2.派生类继承了抽象类:
1.派生类如果没有全部实现基类中的所有纯虚函数,它也是抽象类!
2.派生类如果全部实现基类中的所有纯虚函数,它就是普通类,并可以实例化对象!

应用:每个派生类都要对纯虚函数进行重写!!!
抽象基类规定虚函数特点!

 

1.即运行时多态!编译时不知道调用哪个函数 运行时才知道哪个函数
2.真正的多态的动态多态

虚析构函数:
1. 基类指针指向堆空间的派生类对象,并通过基类指针释放堆空间的派生对象时,如果基类的析构函数不是虚析构函数,则派生类对象的析构函数不会被调用!!!
一句话:一个类要作为基类,他的析构函数就定义为虚析构函数!!!

 

 

 


<C++扩展-异常>
——————————————————————————————————————————————————————————————————————————————————————————
本质:异常是程序在执行期间产生的问题

c++异常关键字
1.throw :抛出异常
1.在代码的任何地方主动抛出异常,可以是任意表达式,表达式类型是抛出的类型
int fun(int a, int b)
{
if(b){
return a/b;
}else{
//抛出异常
throw "抛出异常!" // throw 10 throw后面可以是任意类型值
}
}
//异常没处理,就会终止程序
try{
fun(1,0);
}
catch(,,,)
{
cout<<"产生了异常"<<end;
}
2.函数: 1.成功 2.失败 3.失败原因(抛出具体异常)

2.try :尝试执行
//避免程序被异常退出,检测发生异常!
try{
//逻辑
}
catch(...){
//捕获异常
cout<<"产生了异常"<<end;
}

3.catch :捕获异常
1.捕获数据类型异常
try{
fun(1,0);
cout << "run ok" <<endl; //如果上面产生异常,则不会被执行,直接被跳转到catch!
}
catch(const char* msg) //指定捕获抛出异常的类型! 和避免程序被终止掉
{
cout<<msg<<end;
}
catch(?)
{
}
... //尝试多个catch获取不同的异常
2.catch(,,,)表示:可以捕获所有类型的异常

4.自定义异常:throw + 自定义异常类!
class FHeaderErr : public exception
{
public:
const char* what()const throw()
{
cout << "head error" <<endl;
}
}
//应用:
throw FHeaderErr //打印出 "head error"
1.异常没处理,系统会自动终止。
2.异常其实就是一个类!
3.用户可以自己抛出异常,系统出错也可以自动抛出异常。
4.catch()参数也可以是对象,用引用对象最好。
5.catch可以避免程序产生异常,系统被终止掉!
6.异常作用:
1.帮助分析问题。
2.帮助解决函数返回值成功和失败和异常的意义。
7.异常:
1.标准异常: c++提供标准异常类,系统默认
2.自定义异常:throw + 自定义异常类!

5. <C++扩展-String>
——————————————————————————————————————————————————————————————————————————————————————————
1.字符串的打包拆包分割
2.字符串在c语言没有数据类型
3.String类支持: 1.字符串连接,大小比较,查找,提取,插入,替换。
2.数组灵活性:[]下标法访问
4.string对象保存了字符串。
5.string 构造对象:
1.string s1;
2.string s2("str");
3.string s3(4,'k');
4.string s4("123",1,3);
5.不支持字符和整型。
6.string 对象赋值:
1.s1 = "hellow";
2.s1 = 'a'; //实际结果为"a"
3.s1.assign(s2); //"hellow"
7.string 对象拼接:
1.s3 = s1 + s2; //s2拼接到s1后面
2.s1 = to_string(各种类型)
3.s1.append(s2) //拼接字符串,还可以返回对象引用
8.string 对象比较:
1.s1 > s2 ? 1 : 2;
2.s1.compare();
9.string 对象查找:
1.s1.find();
2.s1.rfind();
10.string 对象替换:
1.s1.replace();
11.string 对象删除:
1.s1.erase();
12.string 对象插入:
1.s1.insert();

2.数组:
1.优点:内存连续,可以下标法访问。 缺点:长度固定,定义完成不能扩容!
————> 动态数组:自动扩容,销毁动态数组,指定位置插入/删除,删除指定元素
Array类设计:后续看!!!

 

5. <C++扩展-内存管理>
——————————————————————————————————————————————————————————————————————————————————————————
形式:指针变量 = new 类型(常量)
指针变量 = new 类型 [表达式]
int *p = new int(10); //申请1个int空间并初始化值为10 ()初始化空间!
int *p = new int[10]; //申请10个int大小空间

delete 指针变量
delete[] 指针变量
delete p;
delete[]p;

1.malloc和free是库函数,本质是函数!
2.new和delete是运算符,不是函数,因此执行效率高(不是函数,不用压栈弹栈)
3.malloc/Free不会触发类的构造/析构函数,而new/delete会触发类的构造/析构函数!
4.new+类,也会调用此类的构造函数;new+类[5],则会调用此类的5次构造函数
5.从堆空间分配一块类型大小存储空间,返回首地址
6.常量为初始化值,可省略;数组对象不能指定初始值
7.new失败了怎么办
8.new/delete是操作符可以重载,malloc/free不能!
9.delete不能用于void*(找不到对象,找不到析构)

 

 

<bool增强>
——————————————————————————————————————————————————————————————————————————————————————————
bool a = true ; 1
bool b = false; 0

sizeof(a) = 一个字节;


<三目增强>
——————————————————————————————————————————————————————————————————————————————————————————
c = a < b ? a : b

(a<b?a:b = 10)
三目运算符可以当左值;c不行!

 


<枚举增强>
——————————————————————————————————————————————————————————————————————————————————————————
enum nu {
aa,
bb,
cc,
};

enum nu p = aa;(只能赋值枚举类型!)
<静态>
——————————————————————————————————————————————————————————————————————————————————————————
静态成员变量:

声明: static 数据类型 成员变量; //在类的内部
初始化:数据类型 类名::静态数据成员=初值;//在类的外部
调用: 类名::静态数据成员
类对象.静态数据成员

静态成员函数:

声明:static 函数声明 //在类的内部
调用:类名::函数调⽤用
类对象.函数调⽤用

静态成员函数只能访问静态数据成员。 原因:非静态成员函数,在调用时this 指
针被当作参数传进。而静态成员函数属于类,而不属于对象,没有 this 指针。


#编译器对属性方法的处理机制:
静态存储空间:
处理机制:成员变量和成员函数是分开存储!
成员变量:
普通:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态:存储于全局数据区中(在类里面)
成员函数:存储于代码段


重点:
1.C语言中的内存四区模型仍然有效!
2.CPP中类的普通成员函数都隐式包含一个指向当前对象的this指针。!!!
3.静态成员函数、成员变量属于类!!!
4.静态与普通成员函数的区别:
静态成员函数不包含指向具体对象的指针
普通成员函数包含一个指向具体对象的指针
5.静态成员函数没有this指针,可以通过函数指针指向!
6.Cpp静态成员属于整个类而不是某个对象,静态成员变量只存储一份供所有对象共用
7.所有对象都可以共享它,使用静态成员变量实现多个对象之间的数据共享不会破坏隐藏的原则,保证了安全性还可以节省内存!
8.类的静态成员,属于类,也属于对象,但终归属于类!
9.static成员只能类外初始化
10.静态成员函数,只能访问静态成员数据;因为它可以不用对象通过类名进行访问;因为静态成员函数不属于类函数,只是作用于类的函数,所以只能访问类中静态数据;因为静态成员函数不属于类函数,只是作用于类的函数,所以只能访问类中静态数据

 

 

<this指针>
——————————————————————————————————————————————————————————————————————————————————————————
成员函数隐含定义this指针,接受调用对象的地址,指向调用对象;

#类成员函数形参和类属性,名字相同,通过this指针解决
#类成员函数可通过const修饰


1.把全局函数转化成员函数,通过this指针隐藏左操作数
Test add(Test &t1,Test &t2) -> Test add(Test &t2)

2.把成员函数转换全局函数,多了一个参数
void prit() -> void prit(Test *pthis)

3.函数返回元素和返回引用

如图:


1.类定义对象时,私有成员被分配,公有函数接口通过调用者的地址进行操作
2.this就是当前调用函数对象的地址!
3.this指针不是const text* 类型
4.this指针是一个常指针,是 text const * 类型(只能指向对象本身!可以改变值,不可改变址!)
5.int getk() const :修饰this指针为const text const * 只读
6.this是经典,对象共用代码部分,将数据通过this指针进行对象分离!
7.静态成员函数没有this指针


笔记
——————————————————————————————————————————————————————————————————————————————————————————
1.未初始化全局变量,在bss段(数据全为0)
2.cpp定义全局变量两次——int a;(bss段) int a = 1;(data段) 报错!
3.cpp结构体定义变量-> 结构体名+变量
4.变量随用随定义,如for;
5.对常量取地址,编译器会临时开辟空间,返回临时空间地址(看const)
6.计算机速度:cpu(寄存器)>缓存>内存>硬盘>网线
7.引用在常量区
8.函数重载就是对一个已有的函数赋予新的含义,使之实现新功能,因此,一个函数名就可以用来代表不同功能的
函数,也就是”一名多用”。
9.类代表类型,对象代表对象

 

拷贝构造函数代码
new/delete 代码

 


优质博客:
https://www.runoob.com/

 

posted @ 2025-06-13 21:49  panda_w  阅读(50)  评论(0)    收藏  举报