Class的两个分类
- class without pointer merber(s)
complex
- class with pointer merber(s)
string
1 c/c++关于数据和函数
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/5d3f59a0cb89fb7790dc14cc6fc31769.png)
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/1d2a780fb718602655a29c346c93144e.png)
基于对象:面对的是单一的class的设计
面向对象:面对的是多重classes的设计,classes和classes之间的关系
2 c++程序代码基本形式
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/c10f31be88b55a3c0a5c0a664742713a.png)
延伸文件名不一定是.h
和.cpp
,也可能是其他甚至没有。
2.1 主程序
#include <iostream>
#include "complex.h"
using namespace std;
int main()
{
return 0;
}
2.2 头文件中的防卫式声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
...
#endif
3 complex (without pointer)
3.1头文件布局
#ifndef __COMPLEX__
#define __COMPLEX__
#include<cmath>
/***********forward declarations(前置声明)********/
class ostream;
class complex;
complex&
__doapl (complex* ths, const complex& r);
/*********class declarations(类-声明)**********/
class complex
{
...
};
/**********class definiton(类-定义)**********/
complex::function ...
#endif
3.2 class的声明
class complex //class head
{ //class body
public:
complex (double r = 0, double i = 0)
: re (r), im(i)
{}
complex& operator += (const complex&); //只是一个声明
double real () const {return re;} //直接在这里定义
double imag () const {reutn im;} //有些函数在此直接定义,另一些在body之外定义
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
//使用方法
{
complex c1(2,1);
complex c2;
...
}
3.3 类template(模板)简介
template<typename T>
class complex
{
public:
complex (T r = 0, T i = 0)
: re (r), im(i)
{}
complex& operator += (const complex&);
T real () const {return re;}
T imag () const {reutn im;}
private:
T re, im;
friend complex& __doapl (complex*, const complex&);
};
//使用时指定T的类型
{
complex<double> c1(2.5,1.5); //T绑定为double
complex<int> c2(2,6);
...
}
3.4 inline(内联)函数
如果函数在class body
内定义,自动成为inline候选
,如果在class本体外定义就不是inline。
inline只是对编译器的建议,由编译器决定,如果函数太复杂,编译器就没有能力把函数变成inline。
inline double //如果在body外定义,可以在前面加上inline
imag(const complex& x)
{
return x.imag ();
}
3.5 访问级别
public
:公开的外界能看得到 函数
private
:只有class自己能看得到 数据
封装起来,不希望随便调用
//见 3.2 class声明
{
complex c1(2,1);
cout << c1.re; //错误。数据在private,外部不能直接访问
cout << c1.im;
}
{
complex c1(2,1);
cout << c1.real(); //正确。函数在public,可以调用成员函数来访问数据
cout << c1.imag();
}
3.6 构造函数
如果想要创建一个对象
,构造函数
会被自动调用。
- 构造函数一定要跟类的名字相同
- 它可以拥有参数
- 参数可以有默认值 //如果创建的时候没有指明参数,则使用默认值 其它函数也可以有默认值
- 没有返回类型 //
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 {reutn im;}
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
一个变量数值的设定有两个阶段:初始化、赋值
complex (double r = 0, double i = 0)
{ re = r; im = i; } //赋值 效果等同于初始化,但是效率低
构造函数对应析构函数
,不带指针的类多半不用写析构函数
3.7 overloading(重载)
同名函数可以有很多个(实际不相同)——重载
- 函数重载常常发生在构造函数里
- 如果构造函数有默认值,不能重载为无默认值的函数
// 构造函数、real函数重载比较
class complex
{
public:
complex (double r = 0, double i = 0) //有默认值,当创建对象没有给参数时可以被调用
: re (r), im(i)
{ }
complex () : re(0), im(0) { } //重载不正确,没有给参数时两个构造函数都可以被调用
complex& operator += (const complex&);
double real () const {return re;}
double imag () const {reutn im;}
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
void real(double r) { re = r; } //重载正确
?real@Complex@@QBENXZ
?real@Complex@@QAENABN@Z //两个real名称相同,但编译后的实际名称不同(参数不同)
3.8 Singleton(单例)设计模式
把构造函数放在private,只允许外界创建一个例子
class A
{
public:
static A& getInstance();
setup() {...}
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a;
return a;
}
A::getInstance().setup(); //通过调用这个函数创建对象
3.9 const成员函数
对class里面的函数分为会改变数据
和不会改变数据
两种,不会改变数据内容的加上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 {reutn im;}
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
//如果该加而没加const
{ //正确
complex c1(2,1);
cout << c1.real();
cout << c1.imag();
}
{ //错误
const complex c1(2,1); //对象前面加const,对象的内容不能改
cout << c1.real(); //real有可能改变对象的数据 ,编译不通过
cout << c2.imag();
}
3.10 参数传递、返回值传递
3.10.1 参数传递:pass by value / pass by reference(to const)
- pass value:整个数据传过去,有多少传多少
- 引用底部就是一个指针,传引用相当于传指针那么快
- 最好所有参数传递都传引用
- 引用有指针的效果,当不希望改数据时加const
class complex
{
public:
complex (double r = 0, double i = 0) //pass by value
: re (r), im(i)
{ }
complex& operator += (const complex&); //pass by reference to const
double real () const {return re;}
double imag () const {reutn im;}
private:
double re, im;
friend complex& __doapl (complex*, const complex&);
};
{
complex c1(2,1); //by value
complex c2;
c2 += c1; //by reference
cout << c2;
}
ostream&
operator << (ostream& os, const complex& x) //os pass by reference
{
return os << '(' << real (x) << ','
<< imag (x) << ')';
}
3.10.2 返回值传递:return by value / return by reference(to const)
complex& operator += (const complex&); // return by reference 类型时complex
double real () const {return re;} // return by value 类型时double
不能返回局部变量
的引用,除此之外都能return by reference,尽量传refernece
传递者
无需知道接收者
是以reference
形式接收 3.12.1
inline complex&
__doapl (complex* ths, const complex& r)
{
ths->re += r.re; //第一参数会被改变
ths->im += r.im; //第二参数不会被改变
return *ths; //this本来就存在,可以传引用
}
inline complex&
complex::operator += (const complex& r)
{
return __doapl (this, r);
}
3.11 friend(友元)
friend complex& __doapl (complex*, const complex&);
inline complex&
__doapl (complex* ths, const complex& r)
{
this->re += r.re;
this->im += r.im;//自由取得friend的private成员
return *ths;
}
- 外部想要取数据通过public函数实现,友元friend可以直接拿private的数据。
- 友元效率更快,但是打破了封装。
相同class的各个objects
互为friends
class complex
{
public:
complex (double r = 0; double i = 0)
: re (r), im (i)
{ }
int func(const complex& param)
{ return param.re + param.im; }
private:
double re, im;
};
{
complex c1(2,1);
complex c2;
c2.func(c1); //c2直接取c1的数据
}
3.12 操作符重载
3.12.1 操作符重载-1,成员函数(this)
传递者
无需知道接收者
是以reference
形式接收
inline complex& //声明return by reference
__doapl(complex* ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths; //返回的是一个对象value,不必管以什么形式接收
}
inline complex& //c3+=c2+=c1 返回类型不能是void
complex::operator += (const complex& r) //这里有一个参数this,不能在参数列 写出来,但在函数内能使用
{
return __doapl (this,r);
}
{
complex c1(2,1);
complex c2(5);
c2 += c1; //+=操作符重载
}
3.12.2 操作符重载-2,非成员函数(无this)
在class的body之外,带有class名称的是成员函数
,不带class名称的是全域函数
。
{
complex c1(2,1);
cout << imag(c1);
cout << real(c1);
}
//为了应对三种可能的用法,这里有三个函数
inline complex //返回的是value,不是引用
operator + (const complex& x,const complex& y)
{
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 + (double x,const complex& y)
{
return complex(x + real (y), imag (y));
}
这三个函数不带class名称,是全域函数。不能return by reference,因为他们返回的必定是local object
。
临时对象
typrname();
<<
操作符重载只能是全域函数,
ostream& //返回值类型不能是void
operator << (ostream& os, const complex& x) //os的状态会改变,不能加const
{
return os << '('<< real (x) << ','<< imag (x) << ')';
}
{
cout << c1 << conj(c1); //c1输出到cout的结果还要能接收conj(c1),因此操作符重载返回值不能为void
}
3.13 写class要注意的点
- 构造函数尽量用初值列
- 数据放在private
- 参数尽可能以reference传递,要不要加const
- 返回值尽量以reference传
- 类的body里的函数应该加const就要加
4 string (with pointer)
4.1 三大函数:拷贝构造、拷贝赋值、析构函数
class String
{
public: //不能用编译器自带的拷贝构造、拷贝赋值
String(const char* cstr = 0); //构造函数
String(const String& str); //拷贝构造,接受的是自己的对象
String& operator=(const String& s); //拷贝赋值,接受的是自己的对象
~String(); //析构函数,这个类的对象死亡的时候调用
char* get_c_str() const { return m_data}; //inline(成员函数)
private:
char* m_data; //字符串里面存放一个指针,在需要内存的时候创建另外一个空间来放字符
};
4.1.1 构造函数、析构函数
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]; //new 动态分配一块内存
*m_data = '\0'; //空指针仍要分配一个内存存放结束符号
}
}
inline
String::~String() //析构函数
{
delete[] m_data; //释放掉动态分配的内存
}
{
String s1(); //调用构造函数,在作用域结束时自动调用析构函数
String s2("hello");
String* p = new String("hello"); //动态的创建对象
delete p; //手动释放内存
}
4.1.2 拷贝构造、拷贝赋值
带有指针的class,必须有拷贝构造、拷贝赋值
拷贝构造
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/aadbe5cd7a2d374fac8ed5c53b983514.png)
编译器默认的拷贝构造、拷贝赋值是浅拷贝,只是拷贝对象里值,这里只复制了指针过去,两个对象指向同一个字符串,并且会造成内存泄漏。
inline
String::String(const String& str) //拷贝构造
{
m_data = new char[strlen(str.m_data)+1]; //分配内存
strcpy(m_data, str.m_data); //把内容拷贝过去 深拷贝
} //直接取另一个对象的private data,同一个类的对象互为友元
{
String s1("hello");
String s2(s1); //拷贝构造
// String s2 = s1;
}
拷贝赋值
inline
String& String::operator=(const String&)
{
if(this == &str)//检测自我赋值,不只是为了效率,防止杀掉自己而丢失内存的控制
return *this;
delete[] m_data; //1.杀掉自己
m_data = new char[strlen(str.m_data) + 1]; //2.重新分配内存
strcpy(m_data, str.m_data); //3.拷贝内容
return *this;
}
#include <iostream.h>
ostream& operator<<(ostream& os, const String& str) //output重载 只能是全局函数
{
os << str.get_c_str(); //直接把指针给cout
return os;
}
4.2 stack(栈),heap(堆)
Stack,是存在于某作用域
的一块内存空间。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本体内声明
的任何变量,其所使用的内存块都取自上述stack。
Heap,是指操作系统提供的一块全局
内存空间,程序可动态分配(new)。
class Complex{...};
...
{
Complex c1(1, 2); //内存来自tack
Complex* p = new Complex(3); //内存来自heap,需要手动delete
} //离开作用域时c1死亡
class Complex{...};
...
Complex c3(1,2);
{
Complex c1(1, 2);
static Complex c2(1, 2);
}
c1是stack object
,其生命在作用域结束时也结束了,这种作用域内的object,又称为auto object,因为它会被自动清理(自动调用析构函数)。
c2是static object
,其生命在作用域结束之后仍然存在,知道整个程序结束。
c3是global object
,其生命在整个程序结束之后结束。也可以把他视为一种static object,其作用域是整个程序。
heap object的生命周期
class Complex{...};
...
{
Complex* p = new Complex;
...
delete p;
}
p所指的便是heap object,其生命在它被delete时结束。
如果没有delete
,在作用域结束时p所指的heap object仍然存在,但指针p的声明结束了,造成内存泄漏。
new:先分配内存,再调用构造函数
Complex* pc = new Complex(1, 2);
//new被分解为三个步骤
Complex *pc;
void* mem = operator new(sizeof(Complex));//分配内存
pc = static_cast<Complex*>(mem);//转型
pc->Complex::Complex(1, 2);//构造函数
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/582db18578b6eaa3f2624c56fabc9ebc.png)
delete:先调用析构函数,再释放内存
4.3 动态分配所得的内存块,in VC
new
一个complex
、string
所得到的内存:
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/aec3aa389ea25eb2f92ca189a74cfcfe.png)
- 绿色:类的对象
- 灰色:调试模式下上面32,下面4。release模式下没有灰色模块
- 紫色:cookie,上下各4个字节,用来记录整块内存的大小,方便回收。cookie的值是内存块大小的16进制,因为是16的倍数最低位是0,用最低位为1来表示内存块被分配出去。
- 绿色(pad):在VC里所给的每一个内存块都是16的倍数,不够需要填补
new一个array
所得到的内存
![还在路上,稍等...](https://i-blog.csdnimg.cn/blog_migrate/f40e81f671575495b118e11f683f1e96.png)
这里多四个字节,用一个整数来记录数组的大小
array new
要搭配array delete
。
如果delete
没有加[]
,如果是不带指针的类,对象会被回收,但带指针的类的对象里的指针所指的内存只有第一个被回收,而造成内存泄漏。
4.3 static
![](https://i-blog.csdnimg.cn/blog_migrate/c6acaedc846654f46f00742acd192084.png)
4.3.1 this point
只要在数据或函数前面加上static
,它就成为静态数据
或静态函数
。
complex c1,c2,c3;
c1.real(); ——> complex::real(&c1);
谁调用,谁就是this point
,c1调用,c1的地址就是this point。调用相同的函数,传给它的是不同的地址,才能处理不同的数据,这个地址是this point。
class complex
{
public:
double real () const
{ return this->re; }
private:
double re, im;
}
4.3.2 成员函数与静态函数的区别:
成员函数
有一个隐藏的this point
,不能写进参数,可以在函数里使用,函数里的this可写可不写,不写编译器会自动加上。静态函数
没有this point
,所以不能通过this
指针来访问各个对象,只能处理静态数据。
4.3.3 调用static函数
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()
{
Account::set_rate(5.0);
Account a;
a.set_rate(7.0);
}
如果是静态数据
,一定要在class外加上:
double Account::m_data = 8.0; //定义 有没有初值都可以
使变量获得内存的操作叫定义
。
调用static函数
的方式:
- 通过object调用
- 通过class name调用
4.3.4 补充singleton
class A
{
public:
static A& getInstance{return a;}; //取得唯一的对象
setup(){...}
private:
A(); //任何人不能创建
A(const A& rhs);
static A a; //创建一个对象
...
};
A::getInstance().setup();
如果没有人调用这个函数,静态的a仍然存在
class A
{
public:
static A& getInstance();
setup() {...}
private:
A();
A(const A& rhs);
...
};
A& A::getInstance()
{
static A a; //使用单例的时候才会创建
return a;
}
4.4 cout
class _IO_ostream_withassign:public ostream{
...
};
extern _IO_ostream_withassign cout;
class ostream:virtual public ios
{
public:
ostream& operator<<(char c);
ostream& operator<<(unsigned char c){return (*this)<<(char)c;}
ostream& operator<<(signed char c){return (*this)<<(char)c;}
ostream& operator<<(const char *s);
ostream& operator<<(const unsigned char *s)
{return (*this) << (const char*)s;}
ostream& operator<<(const signed char *s)
{reutrn (*this) << (const char*)s;}
ostream& operator<<(const void *p);
ostream& operator<<(int n);
ostream& operator<<(unsigned int n);
ostream& operator<<(long n);
ostream& operator<<(unsigned long n);
...
};
cout
对每个类型都做了重载,才能打印出各种数据。
4.5 函数模板,function template
stone r1(2,3),r(3,3),r3;
r3 = min(r1, r2); //编译器会对function template进行实参推导,因此不必像class template指出类的名称
template<class T> //class和typename是相通的
inline const T& min(const T& a, const T& b)
{
return b < a ? b : a; //推导出T为stone 调用操作符重载 stone::operator<
//函数模板不用管怎么比大小,是由类的内容决定的
}
4.6 补充namespace
namespace std
{
...
}
标准库所有东西都被包装在std
中。
调用方式:
- using namespace std;
- using std::cin;
- std::cin
5 继承、复合、委托
5.1 Composition(复合)
5.1.1 复合,表示has-a
template <class T, class Sequence = deque<T>>
class queue
{
...
protected:
Sequence c; //底层容器
public:
//以下完全利用c的操作函数完成
// 只开放deque的部分功能
bool empty() const {return c.empty();}
size_type size() const {return c.size();}
reference front() {return c.front();}
reference back() {return c.back();}
//deque是两端可进出,queue是末端进前端出(先进先出)
void push(const value_type& x) {c.push_back(x);}
void pop() {c.pop_front();}
};
把deque<T>
替换进去
class queue里面有deque,has-a的关系。
黑色菱形表示有
,queue拥有了、容纳了deque这个类
Adapter设计模式
:queue拿deque的部分功能改装为一个新的class
5.1.2 从内存来看composition的关系
![](https://i-blog.csdnimg.cn/blog_migrate/42e46791ca14232326e0ec1b56ecf0d6.png)
内存上是包含的关系
5.1.3 复合关系下的构造和析构
![](https://i-blog.csdnimg.cn/blog_migrate/80c071c4e84ecd995afe21928a2ba23d.png)
- 构造由内而外
container
的构造函数首先调用component
的default构造函数,然后才执行自己。 - 析构由外而内
container
的析构函数首先执行自己,然后才调用component
的析构函数。
5.2 Delegation(委托). Composition by reference
![](https://i-blog.csdnimg.cn/blog_migrate/42dc20017febd321e9c88e7979809d45.png)
左边有一个指针
指向右边,不是完全包含的关系,两边寿命不一致,在任意时间可以调用。
pimpl
(handle/body)编译防火墙:
左边只是对外的接口,真正的实现都在右边做,左边调用右边类的函数。右边类的变动不会影响左边。
a、b、c共享一份内存。当a想要改变数组“hello”时复制一份给它。copy on wright
5.3 Inheritance(继承)
5.3.1 表示is-a
![](https://i-blog.csdnimg.cn/blog_migrate/6f8f7f25a8ae8b4ea59fd17e0f1ae58d.png)
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node:public _List_node_base
:public _List_node_base //公共继承语法 共有三种继承
{ //public 继承、private 继承、protected 继承
_Tp _M_data;
};
父类的数据
时可以完整的继承下来,
5.3.2 继承关系下的构造函数和析构函数
![](https://i-blog.csdnimg.cn/blog_migrate/ac2f2f9ae20856eed70a5d2d996e7bac.png)
父类的析构函数必须是virtual。
5.4 Inheritance(继承)with virtual function(虚函数)
语法:只要在任何一个成员函数之前加上virtual,它就成为虚函数
子类继承父类所有的数据
,和函数的调用权
。
父类的函数子类全部可以调用,但是子类要不要重新定义:
从虚函数角度将成员函数分为三种:
- non-virtual 非虚函数:不希望子类重新定义(override,覆写)
- virtual 虚函数:希望子类重新定义它,且对它已有默认定义。
- pure virtual 纯虚函数:希望子类一定要重新定义它,你对它没有默认定义(可以有定义)。
template method
5.5 继承+复合关系下的构造和析构
![](https://i-blog.csdnimg.cn/blog_migrate/20a1741f2d101405415be3247560c298.png)
上图:
调用Derived时
构造函数顺序:Base->Component->Drived
析构函数顺序:Drived->Component->Base