1.C++ 编程简介
你该具备怎样的基础
- 曾经学过某种程序语言(C语言最佳)
- 变量(variables)
- 类型(types):int,float,char,struct. ⋅ ⋅ ⋅ \cdot\cdot\cdot ⋅⋅⋅
- 作用域(scope)
- 循环(loops)
- 流程控制
- 知道一个程序需要编译、链接才能被执行
- 知道如何编译和链接(如果建立一个可执行程序)
我们的目标
- 培养正规、大气的编程习惯
以良好的方式编写C++ class(基础对象)- class without pointer members
- Complex
- class with pointer members
- String
- class without pointer members
- 学习Classes之间的关系(面向对象)
- 继承(inheritance)
- (composition)
- (delegation)
2.头文件与类的声明
延伸文件名(extension file name)不一定是.h或.cpp,也可能是.hpp或其他或甚至无延伸名。
Header(头文件)中的防重复声明
complex.h
#ifndef __COMPLEX__
#define __COMPLEX__
#endif
class的声明
由于在方法中可能会使用到自定义类型,需要提前声明,这种声明为前置声明
。
class template(模板)简介
template<typename T>
class complex
{
public:
complex (T r = 0,T i = 0):re(r),im(i){}
compplex& operator += (const complex&);
T real () const {return re;}
T imag () const {return im;}
private:
T re,im;
friend complex& __doapl(complex*,const complex&);
};
// 调用
{
complex<duuble> c1(2.5,1.5);
complex<int> c1(2,5);
}
3.构造函数
不带指针的多半不会写析构函数。
-
方式一:
complex (double r = 0,double i = 0):re(r),im(i){}
-
方式二:
complex (double r = 0,double i = 0){ re = r;im =i;}
注意
:虽然二者实现的效果一致,但是方式一
效率高于方式二。这种特殊的写法只有构造函数有:re®,im(i),被称为(初值列,初始列)`
inline(内联)函数
函数太复杂没办法inline,以空间换时间。最终是否inline由编辑器决定。
(构造函数)可以有很多个——函数重载
常常发生在构造函数。
其实在代码中看到是重名函数,但是编译器会进行重新编码,命名成不同名称的名称,这表明实际上不会有真正重名
的两个函数。
4.参数传递与返回值
ctor(构造函数)被放在private里面
类被创建构造函数就会被调用,以至于该类不能被创建;这种特性可用于设计模式中(单例模式)。
const member functions(常量成员函数)
在不改变数据内容的函数后面添加const修饰。
参数传递:pass by value vs. pass by reference(to const)
建议:参数尽量
传引用,效率更高,但传入的引用不需要改变,添加const进行修饰。
返回值传递:return by value vs. return by reference(to const)
建议:返回值尽量
返回引用,效率更高,返回值不发生改变添加const进行修饰。
friend(友元)
友元自由取得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); //查看该方法
}
class body外的各种定义:
什么情况下可以pass by reference
什么情况下可以return by reference
5.操作重载与临时对象
operator overloading(操作符重载1,成员函数)this
所有的成员函数都隐藏一个this
参数,但是在本成员函数中不能添加。
inline complex& __doapl(complex* ths,const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operatpr += (const complex& r)
{
return __doapl(this,r);
}
//调用
{
complex c1(2,1);
complex c2(5);
c2 += c1;
}
return by reference语法分析
传送者无要
知道接收者是以reference形式接收。
c2 += c2 += c1;
class body之外的各种定义(definitions)
inline double imag(const complex& x)
{
return x.imag();
}
inline double real(const complex& x)
{
return x.real();
}
operator overloading(操作符重载2,非成员函数)无this
需求:
{
complex c1(2,1);
complex c2;
c2 = c1 + c2;
c2 = c1 + 5;
c2 = 7 + c1;
}
为了应对client的三种可能用法,这儿对应实现了三个函数。
下面这些函数决不可return by reference,因为它们返回的必定是个local object(临时对象).
inline complex operator + (const complex& x,const complex& y)
{
return complex (real(x) + real(y),imag(x) + imag(y));
}
inline complex operator + (const complex& x,const double y)
{
return complex (real(x) + y,imag(x) + imag(y));
}
inline complex operator + (dobule x,const complex& y)
{
return complex (x + real(y),imag(x) + imag(y));
}
取反重载
inline complex
operator + (const complex& x)
{
return x;
}
inline complex
operator -(const complex& x)
{
return complex(-real(x),-imag(x));
}
共轭复数
inline complex
conj(const complex& x)
{
return complex(real(x),-imag(x));
}
输出
ostream&
operator << (ostream& os,const complex x)
{
return os<<'('<<real(x)<<','<<imag(x)<<')';
}
6.大三函数:拷贝构造,拷贝复制,析构
只要类中带指针,就不能使用默认的拷贝构造和拷贝复制,必须进行重写。
int main()
{
String s1();
String s2("hello");
String s3(s1);//拷贝构造
cout<<s3<<endl;
s3 = s2;//拷贝复制
cout<<s3<<endl;
}
Big Three,三个特殊函数
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;
};
ctor和dtor(构造函数和析构函数)
定义:
注意:申请空间时使用[],与之同时删除的时候也需要使用[]。
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;
}
class with pointer members 必须有copy ctor(拷贝构造)和copy op=(拷贝赋值)
copy ctor(拷贝构造函数)
inline
String::String(const String& str)
{
m_data = new char[strlen(str.m_data)+ 1];
strcpy(m_data,str.m_dta);
}
调用
{
String s1("hello");
String s2(s1);
String s2 = s1;
//注意,第2行和第3行实现的结果是一样的
}
copy assignment operator(拷贝赋值函数)
先删除,然后申请空间,最后进行赋值
inline
String& String::operator=(const String& str)
{
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;
}
调用
{
String s1("hello ");
String s2(s1);
s2 = s1;
}
一定要在operator=中检查是否self assignment
进行拷贝赋值第一件事就是delete,如果两个相同指针相互赋值,则内容已经被清空,无法进行最终的完整赋值。
7.堆,栈与内存管理
output函数
重载<<
和>>
不能用成员函数,只能写普通函数;写成成员函数调用符号在右边,和常规的调用发生冲突,因此写成普通函数调用在左边,和常规保持一致。
#include<iostream.h>
ostream& operator<<(ostream& os,const String& str)
{
os << str.get_c_str();
return os;
}
//调用
{
String s1("hello ");
cout<<s1;
}
所谓stack(栈),所谓heap(堆)
Stack,是存在于某作用域(scope)的一块内存空间(memory space)。例如当你调用函数,函数本身即会形成一个stack用来放置它所接收的参数,以及返回地址。
在函数本身(function body)内声明的任何变量,其所使用的内存块都取自上述stack。
Heap,或谓system heap,是指由操作系统提供的一块global内存空间,程序可分动态分配(dynamic allocated)后其中获得若干空间(blocks)。(使用new
动态取得)
class Complex{...};
...
{
Complex c1(1,2);//c1所占用的空间来自stack
Complex* p = new Complex(3);//Complex(3)是动态获得的,空间来自heap,需要手动进行释放空间
}
stack objects的生命期
class Complex{...};
...
{
Complex c1(1,2);
}
c1便是所谓的stack object,其生命在作用域(scope)结束之后结束。
这样作用域内的object,又称为auto object,因为它会被[自动]清理。
static local objects的生命期
class Complex{...};
...
{
static Complex c2(1,2);
}
c2便是所谓的static object,其生命在作用域(scope)结束之后仍然存在,知道整个程序结束。
global objects的生命期
class Complex{...};
...
Complex c3(1,2);
int main()
{
...
}
c3便是所谓的global object,其生命在整个程序结束之后才结束。你也可以把它视为一种static object,其作用域是[整个程序]。
heap objects的生命期
正确写法
class Complex{...};
...
{
Complex* p = new Complex;
...
delete p;
}
p所指的便是heap object,其生命在它被deleted之后结束。
错误写法
class Complex{...};
...
{
Complex* p = new Complex;
}
以上出现内存泄漏(memory leak),因为当作用域结束,p所指的heap object仍然存在,但指针p的生命却结束了,作用域之外再也看不到p(也就是没进行delete操作)。
new:先分配memory,在调用ctor
Complex* pc = new Complex(1,2);
new编译器转化为:
Complex *pc;
//1.分配内存
void* mem = operator new(sizeof(Complex)); //分配内存(其内部调用malloc(n))
//2.转型(类型转换)
pc = static_cast<Complex*>(mem); //转型
//3.构造函数
pc->Complex::Complex(1,2); //构造函数
delete:先调用dtor,再释放memory
String* ps = new String("Helloo");
...
delete ps;
delete编译器转化为:
//1.析构函数
String::~String(ps); //析构函数
//2.释放内存
operator delete(ps); //释放内存(其内部调用free(ps))
动态分配所得的内存块(momory block),in VC(重点)
前后红色的为cookie,使用cookie记录存储的开始和结束。
灰色部分为调试模式下,与之对应的是非调试模式下。
在其他编译器内存分配很像,但是大小是不一样的。
在vc以下所有的内存块大小一定是16的倍数(16的倍数最后4位都是0,可以借助最后一位使用
)。
cookie有什么用,因为在最后回收的时候,我们只给一个指针,系统没办法确定多大空间。
(左一)其中64的16进制为40,41借助最后一位来表示该内存块空间的状态(是否给出去,或者回收回来),1表示系统已经给出去,对我们的程序而言是获得了。
(左二)其中16的16进制为10,11借助最后一位来表示该内存块空间系统已经给出去了,对我们的程序而言是获得了。
动态分配所得的array,in VC(重点)
一个复数两个double,所以3个复数共6个double,51h为上下cookie,3记录数组的大小(整数类型,4个字节)
奇数个为调试模式,偶数个为非调试模式。
左三左四的箭头为指针。
array new 一定要搭配array delete
在构造函数和析构函数中我们说到申请空间时使用[],与之同时删除的时候也需要使用[]
,要不然会发生内存泄露,此处我们说明为什么会。
正确做法:
String* p = new String[3];
...
delete[] p;//唤起3次dtor
错误做法:
String* p = new String[3];
...
delete p;//唤起1次dtor
差别点:
因为都含有cookie,所有两种方式都能够被删除,但是如果不进行搭配,正确做法进行删除时编译器能够知道需要进行三次删除,调用3次析构函数;而错误做法编译器不知道要删除的是数组,所以只会调用1次析构函数,造成除第一个元素请删除外,其他元素内存组成泄露
。
编程实例
头文件
class String
{
public:
String(const char* cstr = 0);
String(const String& str); //拷贝构造
String& operator=(const String& str); //拷贝赋值
~String();
private:
char* m_data;
}
实现(对应array new和array delete)
inline //尽量让我们的函数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(const String& str)
{
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data,str.m_data);
}
inline
String& String::operatpr=(const String& str)//引用
{
if(this == &str)//取地址
return *this;
delet[] m_data;
m_data = new char[strlen(str.m_data)+1];
strcpy(m_data,str.m_data);
return *this;//可以使用void无返回,但是在连串赋值的情况下会出错
}
inline
String::~String()
{//一般关闭窗口或者关闭问题,没有则不需要
delete[] m_data;
}
注意: 1.申请空间记得+1,为字符串的结束符; 2.拷贝赋值函数无返回值,在连串赋值的时候会出错。
8.类模板,函数模板,及其他
进一步补充:static
静态成员函数没有this
pointer(this指针)
调用static的函数方法有二:
- 通过object调用
- 通过cass name调用
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);
}
进一步补充:把ctors放在private里
Singleton(单例模式),只存在一个特定的对象a
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);
static A a;
...
};
A& A::getInstance()
{
static A a;
return a;
}
进一步补充: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);
ostream& operator<<(const unsigned char *s)
{return (*this)<<(const char*)s;}
ostream& operator<<(const signed char *s);
{return (*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);
...
}
进一步补充:class 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 {return im;}
private:
T re, im;
friend complex& __doapl(complex*, const complex&);
};
调用(这样会出现两份代码)
{
complex<double> c1(2.5, 1.5);
complex<int> c2(2, 6);
...
}
进一步补充:function template,函数模板
举例(此处不需要指明类型,编译器会对function template进行实参推导)
class stone
{
public:
stone(int w,int h,int we)
: _w(w), _h(h), _weight(we)
{ }
bool operator< (const stone& rhs) const ///
{ return _weight < rhs._weight; }
private:
int _w, _h, _weight;
};
stone r1(2,3), r2(3,3), r3;
r3 = min(r1, r2);
实现
template <class T>
inline
const T& min(const T& a, const T& b)
{
return b < a ? b : a;
}
实参推导的结果,T
为stone
,于是调用stone::operator<
注意:typename
和class
都可以使用。
进一步补充:name
格式:
namespace 命名
{
...
}
调用方式:
- using directive
#include <iostream.h>
using namespce std;//全部打开
int main()
{
cin << ...;
cout << ...;
return 0;
}
- using declaration
#include <iostream.h>
using std::cout;//使用cout不需要全名
int main()
{
std::cin << ...;
cout << ...;
return 0;
}
- 每个加入
#include <iostream.h>
int main()
{
std::cin << ...;
std::cout << ...;
return 0;
}
更多细节要深入
- operator type() const;
- explicit complex(…): initialization list{}
- pointer-like object
- function-like object
- Namespace
- template specialization
- Standard Library
- variadic template(since C++11)
- move ctor (since C++11)
- Rvalue reference(since C++11)
- auto(since C++11)
- lambda(since C++11)
- range-base for loop(since C++11)
- unordered containers(since C++11)
- …
9. 组合与继承
- Inheritance(继承)
- Composition(复合)
- Delegation(委托)
Composition(复合),表示has-a
template <class T,class Sequence = deque<T> >
class queue{
...
protected:
Sequence c; //底层容器
public:
//以下完全利用c的操作函数完成
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(); }
};
图表:一个里面有另一个东西(黑色箭头表示有东西)
替换上面的c
定义代码操作:
Adapter
template <class T>
class queue{
...
protected:
deque<T> c; //底层容器
public:
//以下完全利用c的操作函数完成
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是双端队列,queue是队列。
从内存角度分析
注意下侧是层层递进关系。
sizeof :40
template <class T>
class queue{
protected:
deque<T> c;
...
};
↓↓↓
sizeof:16*2+4+4
template <class T>
class deque{
protected:
Itr<T> start;
Itr<T> finish;
T** map;
unsigned int map_size;
};
↓↓↓
sizeof:4*4
template <class T>
struct Itr{
T* curr;
T* first;
T* last;
T** node;
...
};
Composition(复合)关系下的构造和析构
构造由内而外
Container的构造函数首先调用Component的default构造函数,然后才执行自己。
Container::Container(...):Component(){...};
先执行Component,然后执行Container。
析构由外而内
Container的析构函数首先执行自己,然后才调用Component的析构函数。
Container::~Container(...):{...~Component()};
先执行~Container,然后执行Component。
10.Delegation(委托),Composition by reference.
//file String.hpp
class StringRep;
class String{
public:
String();
String(const char* s);
String(const String& s);
String &operator=(const String* s);
~String();
.....
private:
StringRep* rep;//pimp1(见下面定义)
};
//file String.cpp
#include "String.hpp"
namespace{
friend class String{
StringRep(const char* s);
~StringRep();
int count;
char* rep;
};
}
StringRep* rep
在类中用指针相连,生命周期不一致。
桥接(Bridge)模式,又称Handle/Body模式
在左手边对外不变进行调用(客户端),右边单独进行实现(服务端)。
Inheritance(继承),表示is-a
struct _List_node_base
{
_List_node_base* _M_next;
_List_node_base* _M_prev;
};
template<typename _Tp>
struct _List_node
: public _List_node_base
{
_Tp _M_data;
};
struct _List_node: public _List_node_base
:结构体继承,C++有三种(public,private,protected)继承方式,最有用的是public继承。
继承最有价值是和虚函数搭配。
Inheritance(继承)关系下的构造和析构
注意:只要你的类以后或者现在要变为父类,就必须把它的析构函数设置为virtual。
构造由内而外
Derived的构造函数首先调用Base的default构造函数,然后才执行自己。
Derived::Derived(...):Base(){...};
先执行Base,然后执行Derived。
析构由外而内
Derived的析构函数首先执行自己,然后才调用Base的析构函数。
Derived::~Derived(...):{...~Base()};
先执行~Derived,然后执行Base。
Inheritance(继承)with virtual functions(虚函数)
non-virtual函数:你不希望Derived class重新定义(override,覆盖)它。
virtual函数:你希望derived class重新定义(override,覆盖)它,且你对它已有默认定义。
pure virtual函数:你希望Derived class一定要重新定义(override覆盖)它,你对它没有默认定义(其实纯虚函数有定义,此处不进行说明)。
class Shape{
public:
virtual void draw() const = 0; //pure virtual
virtual void error(const std::string& msg); //impure virtual
int objectID() const; //non-virtual
...
};
class Rectangle: public Shape { ... };
class Ellipse: public Shape { ... };
模板方法模式(设计模式之一),虚函数的经典用法之一。
myDoc.opFileOpen();
//实际调用规则:
CDocument::OnFileOpen(&myDoc);
代码示例,对应上策的示意图:
#include<iostream>
using namespace std;
class CDocument
{
public:
void OnFileopen()
{//这是个算法,每个count输出代表一个实际动作
cout<<"dialog.."<<endl;
cout<<"check file status..."<<endl;
cout<<"open file..."<<endl;
Serialize();
cout<<"close file..."<<endl;
cout<<"update all views.."<<endl;
}
virtual void Serialize(){};
};
class CMDoc:public CDocument
{
public:
virtual void Serialize()
{//只有应用程序本身才知道如如何1读取自己的文件格式
cout<<"CMDoc::Serialize()"<<endl;
}
};
int main()
{
CMyDoc myDoc(); //假设对应(File/Open)
myDoc.onFileOpen();
}
Inheritance+Composition关系下的构造和析构
对应的作业,判断谁先谁后。
Delegation(委托)+Inheritance(继承)
应用场景:对应于一个更改,其余部分跟着更改。
class Observer
{
public:
virtual void update(Subject* sub,int value)=0;
};
class Subject
{
int m_value;
vector<Observer*> m_views;
public:
void attach(Observer* obs)
{
m_views.push_back(obs);
}
void set_val(int value)
{
m_value = value;
notify();
}
void notify()
{
for(int i = 0;i < m_views.size();++i)
m_views[i]->update(this,m_value);
}
}
Composite(组合设计模式)
应用场景:大套小,小套大。目录里面可以放文件,也可以继续放目录。
我们此处将Primitive认为是文件,Composite为目录。好吧我承认我不太理解。
对应代码
class Component
{
int value;
public:
Component(int val){value = val; }
virtual void add(Component*) {}
};
class Composite:public Component
{
vector<Component*> c;
public:
Composite(int val):Component(val) {}
void add(Component* elem){
c.push_back(elem);
}
}
class Primitive:Public Component
{
public:
Primitive(int val):Component(val){}
}
Prototype(原型模式)
应用场景:该接口(写在前面)用于创建未来对象(之后才开始创建)的克隆。无法new(不知道对象名称)。
LSAT:指的是静态。
-:表示为私有。通过构造函数将自身放到父类上去。
clone:用于new自己并返回。
《Design Patterns Explained Simply》
父类代码:
#include<iostream.h>
enum imageType
{
LSAT,SPOT
};
class Image
{
public:
virtual void draw() = 0;
static Image * findAndClone(imageType);
protected:
virtual imageType returnType()=0;
virtual Image *clone()=0;/
//As each subclass of Image is declared,it registers its prototype
static void addPrototype(Image * image)
{
_prototypes[_nextSlot++] = image;
}
private:
//addPrototype() saves each registered prototype here
static Image* _prototypes[10];///
static int _nextSlot;///
};
Image *Image::_prototypes[];//定义(给内存)
int Image::_nextSlot;//定义(给内存)
//Client calls this public static member function when it needs instance of an Image subclass
Image * Image::findAndClone(imageType type)
{
for(int i = 0; i < _nextSlot;i++)
if(_prototypes[i]->returnType() == type)
return _prototypes[i]->clone();
}
子类代码:
class LandSatImage:public Image
{
public:
imageType returnType(){
return LSAT;
}
void draw(){
cout<<"LandSatImage::Draw"<<_id<<endl;
}
//when clone() is called,call the one-argument ctor with a dummy arg
Image *clone(){
return new LandSatImage(1);//new自己
}
protected:
//This is only called from clone()
LandSatImage(int dummy){//(为了区分原有的构造函数,为了区分默认添加一个无用参数)
_id = _count++;
}
private:
//Mechanism for initializing an Image subclass- this causes the default ctor to be called,which registers the subclass's prototypr
static LandSatImage LandSatImage;//✔✔静态的自己
//This is only called when the private static data
LandSatImage()//把自己放到父类上去
{
addPrototype(this);
}
//Norminal "state" per instace mechanism
itn _id;
static int _count;
};
//Register the subclass's prototype
LandSatImage LandSatImage::_landSatImage::
//Initialize the "state" per insatnce mechanism
int LandSatImage::count = 1;
后记
虽然更新完了,但是好多关于设计模式方面的思想并没有完全理解,还得多多理解才好。