C++基础(下)

12 篇文章 0 订阅


前言

接着上篇写。

一、C++面向对象编程

1. 内存四区

① C++类对象中的成员变量和成员函数是分开存储的。
② C++中类的普通成员函数都隐式包含一个指向当前对象的this指针。
③ 静态成员函数、成员变量属于类;
静态成员函数与普通成员函数的区别;
静态成员函数不包含指向具体对象的指针;
普通成员函数包含一个指向具体对象的指针;

2. new和delete

	int *p1 = new int;	  //动态分配4个字节的空间
	int *p2 = new int(3); //动态分配4个字节的空间,并初始化为3
	int *p3 = new int[3]; //动态分配12个字节的空间

	delete p1;
	delete p2;
	delete[] p3;

3. malloc/free和new/delete的联系

① malloc/free只是动态分配内存空间/释放空间。而new/delete除了非陪空间还会调用构造函数和析构函数进行初始化与清理成员;
② 都是动态管理内存的入口;
③ malloc/free是C/C++标准库的函数,new/delete是C++操作符;
④ malloc/free需要手动计算类型大小返回值为void*,new/delete可自动计算类型大小,返回类型的指针;
⑤ malloc/free管理内存失败会返回NULL,new/delete管理内存失败会抛出异常。

4. 探究new/delete

① 接口原型:

	void *operator new (size_t size);
	void operator delete(size_t size);

	void *operator new[](size_t, size);
	void operator delete[](size_t, size);

② new表达式并不直接开辟内存出来,而是通过调用operator new来获得内存,而operator new获得的内存实质上还是用malloc开辟出来的;
③ delete表达式也不是直接去释放内存,实际上delete[]做了这样几件事情:

	AA* pA = new AA[10];
	delete[] pA;
a. 依次调用pA指向对象数组中的每个对象的析构函数,共10次;
b. 调用operator delete[] (), 它将再调用operator delete;
c. 底层用free执行operator delete表达式,依次释放内存。

5. new/delete和malloc/free混合使用

a. malloc/delete
	AA* p1 = (AA*)malloc(sizeof(AA));
	delete p1;//没用报错,但是不建议使用,容易引起混肴

	AA* p2 = (AA*)malloc(sizeof(AA));
	delete[] p2;//报错
b. delete,delete[]之间混肴
	AA* p3 = new AA;
	free(p3);//不报错,但未清理干净,p3构造函数开辟的空间没有被释放

	AA* p4 = new AA[10];
	delete p4;//崩溃卡死,释放位置被后移4字节。同时只调用了一次析构函数

	AA* p5 = new AA;
	delete[] p5;//报错,非法访问内存

二、操作符重载

1.操作符重载基础

运算符函数是一种特殊的成员函数或友元函数,重载为成员函数,解释为:

	ObjectL.operator op(ObjectR)

左操作数由Object L通过this指针传递,右操作数由参数ObjectR传递重载为友元函数,解释为:

	operator op(ObjectL, ObjectR)

2. 为什么要有操作符重载

	class Complex
	{
		int a,b;
	}

	Complex C1, C2;
	a = a + b;//int 是基础类型,编译器已经为这些类型提供+ 操作了
	C1 = C1 + C2;
	//C1的类型是Complex,这种类型是自定义类型,编译器无法知道该如何去假发,此时C++编译器给我们提供了一个机制,实现自定义类型相加
	
	//前置--
	Complex& operator--()
	{
		this->a--;
		this->b--;
		return *this;
	}

	//前置++
	Complex& operator++()
	{
		this->a++;
		this->b++;
		return *this;
	}

	//后置--
	Complex operator--(int)
	{
		Complex tmp = *this;
		this->a--;
		this->b--;
		return tmp;
	}

	//后置++
	Complex operator++(int)
	{
		Complex tmp = *this;
		this->a++;
		this->b++;
		return tmp;
	}
操作符重载的三个步骤(通过类的成员函数,完成操作符重载)
	a. 要承认操作符重载是一个函数,要写函数原型
	b. 写出函数调用语言c1.operator-(c2);
	c. 完善函数原型。

3. 项目开发操作符重载的难点

int& operator[](int i);

/*
数组操作符的应用场景由两个:
a. 放在等号的右边	int operator[] (int i);
b. 放在等号的左边	int& operator[] (int i);

*/
= 操作符的两个应用场景:
a. p3 = p2; 不需要用operator=这个函数的返回值
	Array operator= (Array &p2);
b. p1 = p3 = p2; 需要用operator=这个函数的返回值
	Array& operator= (Array &p2);
bool operator==(Array &a2);
bool operator!=(Array &a2);

int&	Array::operator[] (int i)
{
	return mSpace[i];
}

//Array a3(20);
//a3  = a2;
Array& Array::operator= (Array &a2)
{

	printf("Array 执¡ä行D=操¨´作Á¡Â\n");
	if (this->mSpace != NULL)
	{
		delete[] mSpace;
		mLength = 0;
	}


	this->mLength = a2.mLength;
	this->mSpace = new int[a2.mLength];

	for (int i = 0; i < a2.mLength; i++)
	{
		mSpace[i] = a2[i];
	}

	return *this;

}

bool Array::operator==(Array &a2)
{
	//length

	if (this->mLength != a2.mLength)
	{
		return false;
	}
	for (int i = 0; i < a2.mLength; i++)
	{
		if (this->mSpace[i] != a2[i])
		{
			return false;
		}
	}
	return true;
}

bool Array::operator!=(Array &a2)
{
	return !(*this == a2);
}

三. 继承

1. 继承的基本概念

面向对象中的继承指类之间的父子关系:
a. 子类拥有父类的所有成员变量和成员函数;
b. 子类就是一种特殊的父类;
c. 子类对象可以当作父类对象使用;
d. 子类可以拥有父类没有的方法和属性。

2. 继承中的构造函数和析构函数

C++中子类对外访问属性表,访问级别:

publicprotectedprivate
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateprivateprivateprivate

代码如下(示例):

3. 继承模型

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

继承与组合混搭情况下,构造和析构调用原则
原则: 先构造父类,再构造成员变量、最后构造自己
先析构自己,在析构成员变量、最后析构父类

#include <iostream>

using namespace std;
//子类对象如何初始化父类成员
//继承中的构造和析构 
//继承和组合混搭情况下,构造函数、析构函数调用顺序研究
class Object
{
public:
	Object(const char* s)
	{
		cout << "Object()" << " " << s << endl;
	}
	~Object()
	{
		cout << "~Object()" << endl;
	}
};

class Parent : public Object
{
public:
	Parent(const char* s) : Object(s)
	{
		cout << "Parent()" << " " << s << endl;
	}
	~Parent()
	{
		cout << "~Parent()" << endl;
	}
};

class Child : public Parent
{
protected:
	Object o1;
	Object o2;
public:
	Child() : o2("o2"), o1("o1"), Parent("Parameter from Child!")
	{
		cout << "Child()" << endl;
	}
	~Child()
	{
		cout << "~Child()" << endl;
	}
};

void run()
{
	Child child;
}

int main(void)
{
	run();

	system("pause");
	return 0;
}

在这里插入图片描述

四. 多态

1. 多态的基本概念

调用同样的语句有多种不同的表现形态。
在这里插入图片描述

2. 多态成立的三个条件

a. 要有继承;
b. 要有函数重写,含virtual关键字;
c. 父类指针(父类引用)指向子类对象。

3. 函数重载与函数重写

函数重载:
	必须在同一个类中进行;
	子类无法重载父类的函数,父类同名函数将被名称覆盖;
	重载是在编译期间根据参数类型和个数决定函数调用。

函数重写:
	必须发生于父类与子类之间;
	并且父类与子类中的函数必须由完全相同的原型;
	使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义);
	多态是在运行期间根据具体对象的类型决定函数调用。

4. 多态原理探究

当类中声明虚函数时,编译器会在类中生成一个虚函数表,
虚函数表是一个存储类成员函数指针的数据结构,
虚函数表是由编译器自动生成与维护的,
virtual成员函数会被编译器放入虚函数表中,
存在虚函数时,每个对象中都有一个指向虚函数表的指针(vptr指针)。

虚函数表指针(vptr)被编译器初始化的过程:
a. vptr指针是分布完成的:
	首先调用父类的构造函数,初始化子类的vptr指针;
b. 如果在父类里面调用多态函数,此时,子类的vptr指针正好指向父类的虚函数表,所以产生不了多态;
c. 只有当对象的构造完全结束后vptr的指向才最终确定。
#include <iostream>
using namespace std;

class Parent01
{
protected:
	int i;
	int		j;
public:
	virtual void f()
	{
		cout << "Parent01::f" << endl;
	}
};


class Child01 : public Parent01
{
public:
	int k;
public:
	Child01(int i, int j)
	{
		printf("Child01:...do\n");
	}

	virtual void f()
	{
		printf("Child01::f()...do\n");
	}
};

void howToF(Parent01 *pBase)
{
	pBase->f();
}


//多态是靠迟绑定实现的(vptr+函数指针实现)
int main()
{
	int i = 0;
	Parent01* p = NULL;
	Child01* c = NULL;

	//不要把父类对象还有子类对象同事放在一个数组里面
	Child01 ca[3] = { Child01(1, 2), Child01(3, 4), Child01(5, 6) };

	//不要用父类指针做赋值指针变量,去遍历一个子类的数组。

	//把数组的首地址,赋给基类指针
	p = ca;
	//把数组的首地址,赋给子类指针
	c = ca;

	p->f();
	c->f(); //有多态发生

// 	p++;
// 	c++;
// 
// 	p->f();//有多态发生
// 	c->f();

	for (i = 0; i < 3; i++)
	{
		howToF(&(ca[i]));
	}


	system("pause");
	return 0;
}

在这里插入图片描述

5. 为什么要在析构函数前加virtual

class AA
{
public:
	AA(int a= 0)
	{
		this->a = a;
		print(); 
	}
	virtual ~AA()
	{
		cout<<"父类析构函数do"<<endl;
	}
	virtual void print()
	{
		cout<<"父类的"<<"a"<<a<<endl;
	}

protected:
	int a ;
};

class BB : public AA
{
public:
	BB(int a= 0, int b = 0)
	{
		this->a = a;
		this->b = b;

	}

	~BB()
	{
		cout<<"子类析构函数do"<<endl;
	}
	virtual void print()
	{
		cout<<"子类的"<<"a"<<a<<"b"<<b<<endl;
	}
private:
	int b ;
};

//如果想通过父类指针 执行 所有的子类对象的析构函数,那么需要在父类析构函数前加上virtual关键字
//把父类的析构函数变成虚析构函数 
void howToDelete(AA *pBase)
{
	//
	delete pBase;
}
void main()
{
	BB *b1 = new BB(1, 2);
	
	b1->print();

	howToDelete(b1);
	//子类对象的时候,
	//delete b1;
	system("pause");
}

在这里插入图片描述

五. 抽象类(接口类)

绝大多数面向对象语言都不支持多继承;
绝大多数面向对象语言都支持接口类的概念;
C++中没有接口的概念;
C++中可以使用纯虚函数实现接口;
接口类中只有函数原型定义,没有任何数据的定义。

1. 纯虚函数和接口类基础

class SocketIF
{
public:
	//客户端初始化 获取handle 上下文信息
	virtual int cltSocketInit() = 0;

	//客户端发报文
	virtual int cltSocketSend( unsigned char *buf ,  int buflen ) = 0;

	//客户端收报文
	virtual int cltSocketRev( unsigned char *buf , int *buflen ) = 0;

	//客户端释放资源
	virtual int cltSocketDestory() = 0;
public:
	virtual ~SocketIF()
	{

	}
};

class SocketImp1 :public SocketIF
{
public:
	SocketImp1(void);
	~SocketImp1(void);

public:
	 int cltSocketInit();

	//客户端发报文
	 int cltSocketSend( unsigned char *buf ,  int buflen );

	//客户端收报文
	 int cltSocketRev( unsigned char *buf , int *buflen );

	//客户端释放资源
	 int cltSocketDestory();

private:
	unsigned char *buf;
	int buflen;
};
C++类和函数的区别(为什么没有handle)

六. 函数指针

1. 语法基础

//定义一个函数类型
typedef int Func(int);
Func MyFunc;

//定义一个指向函数类型的指针类型
typedef int(*MyPFun)(int);
MyPFun pMyFunc;

//直接定义一个函数指针,并且赋值
void (*myf1)() = NULL;

//函数名称就代表函数的入口地址, 函数名称本身就是一个指针
//可以把函数名赋给一个函数指针,通过函数指针进行函数调用
int test(int a)
{
	return a*a;
}


//语法基础
//函数指针做函数参数的两种写法
//第一种写法:
int add(int a, int b);

//第二个函数 是函数指针 做函数参数
//在这个函数里面,就可以通过这个函数指针,去调用外部的函数,形成一个回调
int libFun( int (*pDis)(int a, int b));

typedef int (*MyPFunDemo)(int);
MyPFunDemo pMyFunc;

2. 正向调用

//客户端初始化 获取handle上下
typedef int (*CltSocketInit)(void **handle ); 
//客户端发报文
typedef int (*CltSocketSend)(void *handle , unsigned char *buf ,  int buflen );
//客户端收报文
typedef int (*CltSocketRev)(void *handle , unsigned char *buf , int *buflen );

//客户端释放资源
typedef int (*CltSocketDestory)(void *handle);


//在这个函数里面完成动态库的加载
//利用winapi
void CMFC应用程序动态加载dll项目Dlg::OnBnClickedButton1()
{
	// TODO: 在此添加控件通知处理程序代码
	HINSTANCE hInstance = NULL;
	hInstance=::LoadLibrary("c:/socketclient.dll");
	CltSocketInit cltSocketInit =  (CltSocketInit)::GetProcAddress(hInstance, "cltSocketInit");
	CltSocketSend cltSocketSend =  (CltSocketSend)::GetProcAddress(hInstance, "cltSocketSend");
	CltSocketRev cltSocketRev =  (CltSocketRev)::GetProcAddress(hInstance, "cltSocketRev");
	CltSocketDestory cltSocketDestory =  (CltSocketDestory)::GetProcAddress(hInstance, "cltSocketDestory");

	void *handle = NULL;
	unsigned char buf[100]; 
	int buflen = 10;
	memcpy(buf, "ddddddddddssssssssss", 10);

	unsigned char out[100] = {0};
	int outlen = 0;

	int ret = cltSocketInit(&handle);

	ret = cltSocketSend(handle, buf, buflen);
	ret = cltSocketRev(handle, out, &outlen);
	ret = cltSocketDestory(handle);

	printf("out:%s", out);



	//SQRTPROC* pFunction;
	//VERIFY(hInstance=::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));
	//VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot"));
	//double d=(*pFunction)(81.0);//调用该DLL函数



	AfxMessageBox("dddd");
}

3. 反向调用

回调函数是利用函数指针实现的一种调用机制

回调机制原理
当具体事件发生时,调用者通过函数指针调用具体函数
回调机制的将调用者和被调函数分开,两者互不依赖

任务的实现 和 任务的调用 可以耦合  (提前进行接口的封装和设计)

七. 泛型编程

1. 泛型编程语法基础

template<typename T>
void swap2(T &a, T &b)
{
	T c;
	c = a;
	a = b;
	b = c;
}

void  main()
{
	//泛型编程的调用方式有两种
	//自动类型推导
	int x = 1, y = 2;
	swap2(x, y);
	printf("x:%d y:%d \n", x, y);

	float x1 = 1.0, y1 = 2.0;

	//具体类型调用
	swap2<float>(x1, y1);
	printf("x1:%f y1:%f \n", x1, y1);
	system("pause");
}

在这里插入图片描述

2. 模板编程

template<class T>
void printfArray(T *a, int num)
{
	cout<<endl;
	for (int i=0; i<num; i++)
	{
		cout<<a[i]<<" ";
	}
}

/*
	1 函数模板可以像普通函数一样被重载
	2 C++编译器优先考虑普通函数
	3 如果函数模板可以产生一个更好的匹配,那么选择模板
	4 可以通过空模板实参列表的语法限定编译器只通过模板匹配
*/

/*
函数模板不允许自动类型转化
普通函数能够进行自动类型转换
*/

/*
函数模板的深入理

	― 编译器并不是把函数模板处理成能够处理任意类型的函数
	― 编译器从函数模板通过具体类型产生不同的函数
	― 编译器会对函数模板进行两次编译
	―在声明的地方对模板代码本身进行编译
	―在调用的地方对参数替换后的代码进行编译
*/

总结

C++基础先复习到这儿,后期继续添加。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值