如何写一个规范、大气的C++类

0.目标

  • 培养正规的、大气的编程习惯
  • 基于对象,以良好的方式编写好一个类,分成两种带指针的类和不带指针的类
  • 面向对象,学习多个类之间的关系,继承、复合、委托

1.C++ programs代码基本形式

在这里插入图片描述
1.尖括号的表示标准库,双引号是自己写的

2.Header(头文件)中的防卫式声明

在这里插入图片描述

在这里插入图片描述

3.class的声明

在这里插入图片描述
防卫式声明

#ifndef __COMPLEX__
#define __COMPLEX__
//前置声明
class ostream;
class complex;

comple& __doapl(complex* ths, const complex& r);
//类声明
class complex
{

};
//类定义
complex::function ...
#endif

在这里插入图片描述
class template(模板)简介

template<typename T>
class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{}
	complex& operator += (const complex&);
	double real() const {return re;}
	double imag() const {return im;}
private:
	T re, im;

	friend complex& __doapl (complex*, const complex&);
};


{
	complex<double> c1(2.5, 1.5);
	complex<int> c2(2, 6);
}

4.inline(内联)函数

函数定义在class body里就构成inline函数
你建议编译器inline不一定真的会成为inline
在这里插入图片描述

5.access level(访问级别)

  • 我们希望数据是封装起来的,不被外界看到
    在这里插入图片描述

6.constructor(ctor,构造函数)

在这里插入图片描述

6.1 构造函数初始化

  • 构造函数尽量初值列(initialization list),而不是赋值
    注意:
    不带指针的类多半不用写析构函数
    在这里插入图片描述
  • 在c++里可以函数重载,在编译器看来两个函数名称不同;在c语言中没有重载
  • 构造函数可以重载,但如果在C++程序中发生“不允许重载构造函数”的错误,可能的原因有以下几种:如果类中已经定义了一个无参的构造函数,则不允许再定义一个带参数的构造函数,此时需要将带参数的构造函数改为使用默认参数来代替。如果类中已经定义了一个带一个或多个参数的构造函数,又想再定义一个和前面函数一致的构造函数,则需要将函数名和参数列表改为不同的名称。

6.2 const member functions(常量成员函数)

  • 类里面的函数分为能改变数据的和不能改变数据的,不改变数据内容的,需要在圆括号后面,花括号前面的加const。不加的会出现问题见图,使用者加const,被告知数据不能改变。但是设计者告知可以变化,前后有矛盾。
    在这里插入图片描述
class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{}
	complex& operator += (const complex&);
	double real() const {return re;}
	double imag() const {return im;}
private:
	double re, im;

	friend complex& __doapl (complex*, const complex&);
};

不加什么后果
{
	complex c1(2,1);
	cout << c1.real();
	cout << c1.imag();
}

{
	const complex c1(2, 1);//对象不可以更改
	cout << c1.real();//对象调用类成员函数,不写const,成员函数告诉你可以改,编译出错
	cout << c1.imag();
}

6.3 参数传递

在这里插入图片描述

  • 参数传进去不修改数值,就要加const

在这里插入图片描述

  • 返回值尽量也要用引用,在类型标识符后面加取地址符号
    在这里插入图片描述
  • 朋友破坏了封装性
    在这里插入图片描述
class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{}
	complex& operator += (const complex&);
	double real() const {return re;}
	double imag() const {return im;}
private:
	double re, im;

	//声明
	friend complex& __doapl (complex*, const complex&);
};

//自由取得friend的private成员
inline complex&
__doapl(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return *ths;
}

//相同class的各个objects互为friends(友元)
class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{}
	//另一个类没有通过调用成员函数,直接使用private里面的数据。原因是:相同class的各个objects互为friends(友元)
	int func(const complex& param)
	{ return param.re + param.im}

private:
	double re, im;
};

{
	complex c1(2,1);
	complex c2;
	
	c2.func(c1);
}
  1. 数据放在private里
  2. 数据以reference来传,要不要加const看情况
  3. 返回值尽量用reference来传
  4. 在类里应该加const要加
  5. 构造函数列表初始化

什么情况下可以 pass by reference。
什么情况下可以 return by reference(结果本来就存在的可以用,local变量死掉了)图片中第一参数本来就在,可以使用返回值引用,但是如果创建出局部变量程序结束就销毁了就不能创建

在这里插入图片描述

class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{/*
		分配内存
		开一个窗口
		开一个文件
	*/}
	#complex():re(0),im(0){}
	/*
	{
		complex c1;
		complex c2();
	}
	1.同名的函数可以存在,编译器里看来不同
	2.函数重载常常发生在构造函数里
	3.当前这个例子是冲突的
	*/
	complex& operator += (const complex&);
	double real() const {return re;}
	double imag() const {return im;}
private:
	double re, im;

	friend complex& __doapl (complex*, const complex&);
};

问题:
哪些情况必须要用列表初始化?

构造函数放在private里(设计模式Singleton,外界只能创建一份)

class  A{
public:
	static A& getInstance();
	setup(){...}

private:
	A();
	A(const A& rhs);
	...
};

A& A::getInstance()
{
	static A a;
	return a;
}
# 外界使用
A::getInstance().setup();

7.操作符重载与临时变量

操作符在c++被认为是一种函数
在这里插入图片描述
所有成员函数都隐藏着this的指针

在这里插入图片描述

  • 返回的是指针指向的obj,接受端以引用的方式接收
  • 返回值要注意数据类型,这样可以实现连加

  • 参数传递多写引用传递(pass by reference),少写值传递

  • 成员函数的返回值多写引用返回(return by reference),少写值返回。当返回的对象在程序结束后销毁,就不能写引用返回

  • 成员函数不修改输入的参数的时候,记得加const。如果不加,使用时候可能会报错

  • 数据一定放在private

其他注意的点:

  1. 相同class的各个objects互为friends(友元)
  2. 构造函数可以重载(小心有时候不能用),可以被放在private,设计模式Singleton,外界不能创建它

重载操作运算符

  1. 写在成员函数中
  2. 写在全局中,重载<<的时候,必须定义在全局中

传过去不想改,就加const

class complex
{
public:
	complex(double r = 0, double i = 0)
	: re(r), im(i)
	{}
	complex& operator += (const complex&);
	double real() const {return re;}
	double imag() const {return im;}
private:
	double re, im;

	friend complex& __doapl (complex*, const complex&);
};

操作符重载-1,成员函数

//接收端也可以return by value,只是速度没有reference快
//调用函数的时候会开辟一个新空间,如果使用引用形式,就可以直接利用原有空间,而不开辟新空间
inline complex&
__doapl(complex* ths, const complex& r)
{
	ths->re += r.re;
	ths->im += r.im;
	return *ths;//参数传入的是一个指针,返回的是指针所指向的object,是个value,传送者无需知道接受者是以reference形式接收
}
//返回为什么不写void,考虑到连续相加的问题
inline complex&
complex::operate += (const complex& r)
{
//不可以在参数列写this,但是可以在里面使用this
	return __doapl(this, r);
}

{
	complex c1(2,1);
	complex c2(5);

	c2 += c1;
	c3 += c2 += c1;
}

操作符重载-2,非成员函数(无this)

全局函数

//为什么这里的函数没有使用return by reference,因为,他们返回的必定是个local object
//下面这些函数绝不可return by reference,因为,它们返回的必定是个local object
inline complex
operator + (const complex& x, const complex& y)
{
//temp object(临时对象)下一行就消失了  typename(),例如int()
	return complex (real(x) + real(y),
					imag(x) + imag(y));
}

inline complex
operator + (const complex& x, double y)
{
	return complex (real(x) + y, imag(x));
}

inline complex
operator + (const complex& x, double y)
{
	return complex (x + real(y), imag(y));
}

{
	complex c1(2,1);
	complex c2;

	c2 = c1 + c2;
	c2 = c1 + 5;//如果写成员函数,不能实现这个功能
	c2 = 7 + c1;
}

有一种特殊情况操作符重载必须使用全局函数(如果写成成员函数,c1 << cout,这不是我们想要的)
cout输出新的类

{
	complex c1(2,1);
	//这里“<<”必须用全局函数
	//为什么要用全局函数。标准库早就写好的cout不可能认识新写的类,所以不能写成成员函数
	cout << conj(c1);
	//c1先输出给cout,留下ostream&,然后再接受,conj出给cout
	cout << c1 << conj(c1);
}
//共轭复数
inline complex conj (const complex& x)
{
	return complex (real(x), -imag(x));
}

#include <iostream>
//参数os不能使用const,因为要改变os,不断往os里扔资料
//注意为什么返回值不是void,而是ostream&,因为连续输出的原因
ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real(x) << ',' 
				<< imag(x) << ')';
}

7.三大函数:拷贝构造,拷贝复制,析构

class without pointer member(s)
class with pointer members(s)

int main()
{
	String s1();
	String S2("hello");
	//拷贝
	String s3(s1);//拷贝构造
	cout << s3 << endl;
	s3 = s2;//拷贝赋值

	cout << s3 << endl;
}
class String
{
public:
	String(const char* cstr = 0);
	String(const String& str);//参数的类型是它自己
	String& operator=(const String& str);//参数的类型是它自己
	~String();
	char* get_c_str() const { return m_data;}
private:
	char* m_data;
};

字符串多长,有两种想法
1.不知道有多长,但是有个结束符号
2.有长度,没有结束符号

在这里插入图片描述

inline 
String :: String(const char* cstr = 0)
{
	if(cstr){
		m_data = new char[strlen(cstr) + 1];
		strcpy(m_data, cstr);
	}
	else{
		m_data = new char[1];
		*m_data = '\0';
	}
}

inline
String::~String()
{
	delete[] m_data;
}
//外部调用
{
	String s1();
	String s2("hello");

	String* p = new String("hello");
	delete p;
}

inline
String:: String(const String& str)
{
	//直接取另一个object的private data(兄弟之间互为friend)
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
}

inline
String& String::operator = (const String& str) 
{
	//检测自我赋值(self assignment)。有两个作用,高效不用运行以下内容,避免报错内存泄露
	if(this == &str)
		return *this;

	delete[] m_data;
	m_data = new char[strlen(str.m_data) + 1];
	strcpy(m_data, str.m_data);
	return *this;
}

8.堆,栈与内存管理

stack object其生命在作用域结束之时结束,自动被清理
static object其生命在作用域结束之后仍然存在,直到整个程序结束
global object其作用域整个程序
heap objects其生命被delete之时结束,如果没有删除,p所指的heap object仍然存在,但指针p的生命结束了,作用域之外再看不到p(也没有机会delete p)

new:先分配memory,再调用ctor
1.分配内存,分配指针指向的内存(这个指针没有创建)
2.转型
3.构造函数,第二步得到的指针指向内存头

delete:
1.先调用dtor,删除(析构函数)动态分配的东西
2.再释放memory,删除那个指针

在这里插入图片描述在vc里内存大小必须是16的倍数,00000041是4乘16加1,1表示使用出去

在这里插入图片描述
在这里插入图片描述不正确的用法,内存泄露的是右边的内存不是左边的内存

拓展补充:类模板,函数模板,及其他

在这里插入图片描述

普通的成员函数都隐藏着this这个指针,某个对象调用某个函数,this就指向这个对象。内存里成员函数只有一个,它要处理很多的对象,就需要靠this指针来找到他要处理的数据在哪里。
静态数据脱离了对象。只有一份。
静态函数没有this,只能处理静态数据,不能处理非静态数据

class Account{
public:
	static double m_rate;//声明
	static void set_rate(const double& x) {m_rate = x;}
}
double Account::m_rate = 8.0;//定义

int main(){
//调用static函数的方式有二:
//(1)通过object调用
// (2) 通过class name调用

	Account::set_rate(5.0);

	Account a;
	a.set_rate(7.0);
} 

在这里插入图片描述

组合与继承

Inheritance
Composition
Delegation(带指针)

在这里插入图片描述虚函数表

虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的。简称为V-Table。在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。这样,在有虚函数的类的实例中这个表被分配在了这个实例的内存中,所以,当我们用父类的指针来操作一个子类的时候,这张虚函数表就显得由为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

1.一般继承(无虚函数覆盖)
在这里插入图片描述
在这里插入图片描述
子类的虚函数
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面。

2.一般继承(有虚函数覆盖)

在这里插入图片描述
在这里插入图片描述我们从表中可以看到下面几点,

1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。

2)没有被覆盖的函数依旧。

3.多重继承(无虚函数覆盖)
在这里插入图片描述在这里插入图片描述1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)

4.多重继承(有虚函数覆盖)
在这里插入图片描述在这里插入图片描述

typedef void(*Fun)(void);
Fun pFun = NULL;
pFun = ( Fun )*( ( int* ) * ( int* )( &b ) + i ); 
//对对象b的地址转化为int* 地址,然后对其解引用,然后 强转为int* 再对其解引用???在强转为函数指针。
//不用(int*)转化的话,数据类型是(Base*)
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值