c++面向对象高级编程(一)
虽然平时C++用的不多,但以后面试以及学校做的项目也是避不开的一点,故学!
视频参考:高清 1080P C++面向对象高级编程(侯捷) P38 24 Basic String使用newextra扩充申请量 (youtube.com)
文章目录
第一部分 基础
二 头文件与类的声明
数据和函数
- 实部/虚部 complex:
- 实部–类似c1、c2,数据只有一个
- 虚部–加减乘除、共轭、正弦
- 字符String
- 是一个指针,指向字符串
代码基本声明
- .h(.hpp)
- .cpp
防卫式声明
防止文件被重复编译
complex.h
#ifdef _COMPLEX_
#define _COMPLEX_
...
#endif
complex-test.h
#include <iostream>
#include "complex.h"
using namespace std;
int main(){
...
}
(头文件)的布局
#ifdef _COMPLEX_
#define _COMPLEX_
#include <cmath>
//forward declaritions
class ostream;
class complex;
class complex
{
...
}
complex :: function ...
#endif
class的声明(declaration)
//head
class complex
//body
{
public:
complex (double r = 0, double i = 0)
: re(r), im(i)
{}
complex& opearator += (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);
complex c2;
...
}
class template(模板)简介
通过传入不同的参数,使得变量类型发生变化
//head
template<typename T>
class complex
//body
{
public:
complex (T r = 0, T i = 0)
: re(r), im(i)
{}
complex& opearator += (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,1);
complex<int> c2;
...
}
三 构造函数
inline(内联)函数
- 简单
- 编译器自动选择
- 函数若在class body里定义完成,便自动成为inline候选人
访问级别
- public:公开,任何位置都可以使用
- private: 私有,只有类内部能使用
构造函数
complex (double r = 0, double i = 0) : re(r), im(i) {}
-
double r = 0, double i = 0
: default argument默认实参- 没有传入参数就为默认实参
-
re(r), im(i)
: initialization list 初值列,初始值- 良好的构造函数写法,时间快,效率高,
- 若在括号里
{re = r ; im = i}
则效率低,时间慢
-
创建对象:
complex c1(2,1); complex c2; complex* p = new complex(4);
重载
//head
class complex
//body
{
public:
complex (double r = 0, double i = 0)
: re(r), im(i)
{}
complex () : re(0), im(0) { }
complex& opearator += (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);
//报错
complex c2;
//正常
complex c3();
...
}
- 构造函数可以存在多个,需要以重载方式呈现(同名不同参)
- 当函数存在重载时,若函数存在初始化,则complex c1会使用该函数构造。因此,当多个函数重载有不含参或含参带初始化的构造函数时,会默认这些函数都为候选人而导致错误。
constructor(ctor,构造函数)被放在private区
-
直接放置,错误
//head class complex //body { public: .. private: double re,im; complex (double r = 0, double i = 0) : re(r), im(i) {} friend complex& __doapl (complex* , const complex&) } { complex c1(2,1); complex c2; ... }
-
设计模式–Singleton 单例模式
- 在public中设置可以访问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();
四 参数传递与返回值
const member functions (常量成员函数)
double real () const { return re; }
double imag () const { return im; }
- const: 不改变函数值,不加会默认可以改变
- 常规声明对象没有影响
- 而若对象被声明为const,函数中的值应该是不可以变化的,需要写const来确保不会变化,否则会报错
所以若无需要修改的变量,尽量给函数写上const
pass by value vs. pass by reference(to const)
- reference 一般速度更快(仅地址),但是值会被修改
- 部分传值(如1、2字节的值),会稍快
- 仅仅想快速传参,为防止修改,可以加上const
- operator << (ostream& os, const complex& x)
return by value vs. return by reference(to const)
- double : 返回的是double类型的值
- complex& : 返回是complex类型的引用
friend 友元函数
-
自由获得friend的private成员
-
相同class的各个objects互为friends(友元)
class body外的各种定义(definitions)
- 什么情况下可以 pass by reference?
- 数据在private
- 该加const就加const
- 什么情况下可以 return by reference?
- 返回值尽量reference
- 返回已经存在的空间
- 若在函数内创建一个值,如果返回引用,函数结束时local被销毁,传出的引用为空。
inline complex&
__dopal(complex& thes, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex&
complex::operator += ( const complex& r)
{
return __dopal (this, r);
}
五 操作符重载与临时对象
operator overloading(操作符重载-1,成员函数)
-
类里面的函数称为成员函数
class complex //body { public: ... //成员函数 complex& opearator += (const complex&); double real () const { return re; } double imag () const { return im; } private: ... }
-
不在class里的函数称为全局函数
//方法
inline complex&
__dopal(complex& ths, const complex& r)
{
ths->re += r.re;
ths->im += r.im;
return *ths;
}
//操作符重载
inline complex&
complex::operator += ( const complex& r)
{
return __dopal (this, r);
}
- this在函数里指的就是左边的对象(c2),引用的complex对象是c1,但是重载时不需要把this写出来
return by reference 语法分析
-
以前:pointer传递需要知道接受的是什么
-
传递者无需知道接收者是以reference形式接收
operator overloading(操作符重载-1,非成员函数)
//操作符重载
inline complex
operator + ( const complex& x , const complex& x)
{
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& x)
{
return complex (x+real(y),imag(y));
}
临时对象 typename();
- 上面这些函数绝不可return by reference,因为它们返回的必定是个local object,是一个临时对象
- 他们的生命周期在下一行前就会结束。
{
complex c1(2,1);
complex c2;
//临时对象
complex();
complex(5,4);//此时complex()就已经结束了
...
cout << complex(3,6);
}
同符号,不同类型的运算
- 对于一个运算符有多个可能的运算,可以写多个函数
特殊重载
- 一些特殊的符号,有重载的限制
- 如 << ,最好使用全局函数修改
六 复习
面向对象程序正规思路:
- 防卫式声明
- 类声明
- 私有区域、公共区域
- 构造函数
- (考虑默认值声明)
- 考虑引用by reference还是传值by value
- 初值列
- 是否要改动,是否加const(很重要)?
- 友元函数
- 内联函数(写里面,写外面)?
- 重载运算符(一般都是右边作用在左边)
- 成员函数
- by reference :+=
- by value:对象+对象
- 非成员函数
- 前面不需要class名称,如<<
- 多重传递,如ostream&传值后再返回ostream&可继续传值(cout << c1 <<endl)
第二部分摘要:
String类实现思路:;
- 类,先放私有的指针类型数据
char* m_data
- 初始化:初值的构造函数
- 非空:
- 分配内存
- 拷贝值
strcpy()
- 空:
- 分配1内存
- 指定*m_data=‘\0’
- 拷贝构造函数
- 分配内存
- 拷贝值
strcpy()
- 拷贝赋值函数
- 自检
- 释放内存
- 分配内存
- 拷贝值
strcpy()
- 返回*this
- 析构函数
- 释放内存m_data
- 字符串要用到cout中,则需要设计一个获得c风格的字符串函数。
七 三大函数Big Three
对象声明数据
-
使用指针如char*,而不是数组(动态分配的思想)
class String { public: String(const char* cstr = 0); char* get_c_str() const { return m_data;} private: char* m_data; }
拷贝构造 s2(s1)
class String
{
public:
String(const char* cstr = 0);
//---
String(const String& str);
//---
private:
char* m_data;
}
拷贝复制(赋值) s2 = s1
浅拷贝的危害:
只拷贝指针!!!如果对象直接拷贝,如s2 = s1,其中s2的数据指针,就变成了指向s1的数据指针,而这样却并没有将s2原来的数据内存删除掉,因此内存会泄露。
因此我们需要深拷贝
class String
{
public:
String(const char* cstr = 0);
//---
String& operator = (const String& str);
//---
private:
char* m_data;
}
-
深拷贝:拷贝赋值函数
为什么要检测自我赋值?
为了效率,也为了正确性。因为我们赋值需要首先删除内存,如果不检测就直接赋值,赋值对象是本身的话,就会先删除再赋值。从而导致无法找到赋值地内容。inline String& String::operator=(const String &str){ //一定要检测自我赋值!!! 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; }
析构函数 ~s1()
常用于动态分配的释放
class String
{
public:
String(const char* cstr = 0);
//---
~String();
//---
private:
char* m_data;
}
-
例子:**深拷贝:**动态分配释放
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; }
八 堆/栈/内存管理
堆 Stack / 栈 heap
-
存在于作用域scope地一块内存空间memory space。
- 调用函数,函数本身会形成stack来防止接受的参数以及返回地址
- 函数本体内声明的任何变量,所使用的内存块都取走上述stack
- 常见对象:
- stack obejct:又称为auto object,它会被【自动】请理,scope在函数内
- static objects。scope全局
- global objects:可以视为一种static object。scope:全局
-
Heap,或所谓sysytem heap,是指由操作系统提供的一块global内存空间,程序可**动态分配(dynamic allocated)**从某中获得若干区块(blocks)
- heap objects : 使用new进行分配,其必须使用delect结束生命周期,否则会造成内存泄漏。
class Complex{...} ... { //stack object所占用的空间来自stack Complex c1(1,2) //new :占用的空间来自heap,dynamic allocated Complex* p = new Complex(); }
new的过程(malloc改写)
先分配memory,再调用ctor
Complex* pc = new Complex(1,2);
编译器转化为:
//分配内存:其内部就是调用malloc
void* mem = operator new(sizeof(Complex));
//转型
pc = static_cast<Complex*>(mem);
//构造函数
pc -> Complex::Complex(1,2);
delete的过程(free的改写)
先调用dtor,再释放memory
delete pc;
编译器转化为:
Complex::~Complex(pc); //析构函数
operator delete(pc); //释放内存:其内部使用Free
内存块分配
动态分配Dynamic alloocated in VC
- 红色:cookie,必须有,记录整块的大小,便于回收
- 如第一个区域64位,16进制为0040,进一位得0041
- 白色:调试模式下带的上面32+下面的8,非Debug则没有
- 蓝色:填补,填空缺
- 内存块一定是16的倍数:52->64
动态分配的array
- 复数:其中含有两个double
- 数据存在一起
Complex* p = new Complex[3];
array new 一定要搭配array delete
-
delete[]
会唤起多次dtor,而delete只会唤起一次dtor -
如果用delete释放内存,则会造成存在指针的对象数组只被释放一次
-
虽然没有指针都可以进行正确释放,但需要时刻保持正确的释放方法
第三部分
十:补充扩展
static
-
函数构成
- data members
- static data members
- non-static member functions
- 通过this->又可以取到多个non-static data members(实例化多个对象)
- static member functions:只能处理静态数据
-
静态数据一定要设置初值
-
调用方法:
- Class Name调用
- object调用
class Acount{ public: static double m_rate; static void set_rate(const double &x){m_rate = x;} } double Account::m_rate = 8.0; int main(){ //Class Name调用 Account::set_rate(5.0); //Object调用 Account a; a.set_rate(7.0); }
-
单例模式
- 正常
class A{ public: static A& getInstance(return a;); setup(){...} private: A(); A(const A& rhs); static A a; ... };
- 更好的单例模式:
- 更好的原因:只有有人调动他时,才会创建单例。
class A{ public: static A& getInstance(); setup(){...} private: A(); A(const A& rhs); } A& A::getInstance() { static A a; return a; }
cout
为什么能输出那么多东西?
- 继承自ostream,所以看ostream
- cout定义了很多函数用来接收各种各样的数据
template 模板类
//head
template<typename T>
class complex
//body
{
public:
complex (T r = 0, T i = 0)
: re(r), im(i)
{}
complex& opearator += (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,1);
complex<int> c2;
...
}
- funtion template
- 方法->模板类->引数推导argument deduction
namespace
- 防止写出同名的方法造成命名冲突或错误引用
namespace std
{
...
}
-
使用方法:
-
using directive
using namespace std; cout<<...;
-
using declaration
using std::cout; cout<<...;
-
std::cout<<...;
-
十一 组合与继承 三大武器
Composition 复合,表示has-a
A拥有b,功能都由b来完成
- 寿命一致
- Adapter设计模式示例:改造函数
template <class T>
class queue{
...
protected:
deque<T> c;
public:
bool empty() const{ return c.empty();}
size_type size() const {return c.size();}
...//返回c的各种方法
}
- Composition下的构造与析构关系
- 构造由内而外
Container::Container(...) : Component(){...}
- 析构由外而内
Container::~Container(...){... ~Component()};
- 构造由内而外
Delegation 委托 Composition by reference
-
寿命不同
-
变动可以有弹性,不管指针指的方法怎么变都不会影响现在的类
-
又称编译防火墙,不用重新编译String的部分,只用编译指rep的部分
-
Handle/Body桥接设计模式举例
//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; //pimpl }
// file String.hpp #include "String.hpp" namespace { class StringRep(){ ... } }
委托可以对外接口一致,String接口永远不变,但是内部实现可以通过修改StringRep改变。
Inheritance 继承,表示is-a
-
父类的数据被完整地继承下来
-
三种继承方式(其他语言一般默认是public)
- public:最重要
- protected
- private
-
内存:
- 外部包着内部
-
构造与析构:
- base class的dtor必须是virtual,否则会出现undefined behaviour
- 构造由内而外
Container::Container(...) : Component(){...}
- 析构由外而内
Container::~Container(...){... ~Component()};
十二 虚函数与多态 virtual functions
-
non-virtual函数:不希望重写
-
virtual函数:希望重写,且有默认定义
-
pure virtual函数:希望重写,且有默认定义
class Shape{ public: virtual void draw() const = 0; //纯虚函数,pure vitual vitual void error(const std::string& msg); //非纯虚函数, impure virtual int objectID() const; //non-virtual }
-
子类可以调用父类函数
- myDoc继承自Document,Document方法可以被子类调用。
myDoc.OnFileOpen()
&&Document.OnFileOpen()
- myDoc继承自Document,Document方法可以被子类调用。
-
继承+复合的构造与析构调用优先级?
class Base { public: Base() { cout << "Base 的 构造函数" << endl; } ~Base() { cout << "Base 的 析构函数" << endl; } }; class Component { public: Component() { cout << " Component 的构造函数" << endl; } ~Component() { cout << " Component 的析构函数" << endl; } }; class Derived : public Base { public : Derived() { cout << "Derived 的构造函数" << endl; } ~Derived() { cout << "Derived 的析构函数" << endl; } Component c; };
第一个图的结果:
- 构造:Base > Component > derived
- 析构:derived > Component > Base
第二个:和前面同理,略。
功能最强大:Delegation + Inheritance
-
举例:多窗口
-
举例:三种view
User Interface设计(Observer观察者设计模式):
- 定义一个存储多个Observer的容器,创建出的Observer都可以放在Observer中。
- 需要提供一个注册和注销的方法Attach(注销还没写)
- 需要提供一个notify函数,用来通知所有的子类更新
class Subject{
int m_value; //要被观察的数据
vector<Observer*> m_views; //观察者列表
public:
void attach(Observer* obs)//注册
{
m_views.push_back(obs);
}
//修改值之后,要通知所有的Observer,让它们做出改变
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); //调用Obeserver的方法,去更新Obeserver的显示。把自己指针传出去
}
}
class Observer{
public:
virtual void update(Subject* sub,int value) = 0; //纯虚函数,让子类的观察者真正去显示这个值
}
十三 委托相关设计
Composite组合设计模式
如果要写一个File System,要装下一些File或者其他组件,可以用。
Prototype原型(机)设计模式
父类想创建未来才定义的子类。
-
父类:
-
子类:
- 返回图片类型Type
- 返回这个class到static的变量中
r Interface设计(Observer观察者设计模式):
- 定义一个存储多个Observer的容器,创建出的Observer都可以放在Observer中。
- 需要提供一个注册和注销的方法Attach(注销还没写)
- 需要提供一个notify函数,用来通知所有的子类更新
class Subject{
int m_value; //要被观察的数据
vector<Observer*> m_views; //观察者列表
public:
void attach(Observer* obs)//注册
{
m_views.push_back(obs);
}
//修改值之后,要通知所有的Observer,让它们做出改变
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); //调用Obeserver的方法,去更新Obeserver的显示。把自己指针传出去
}
}
class Observer{
public:
virtual void update(Subject* sub,int value) = 0; //纯虚函数,让子类的观察者真正去显示这个值
}
十三 委托相关设计
Composite组合设计模式
如果要写一个File System,要装下一些File或者其他组件,可以用。
[外链图片转存中…(img-mA4ZiRDg-1707740361650)]
Prototype原型(机)设计模式
父类想创建未来才定义的子类。
[外链图片转存中…(img-QkJLRJk2-1707740361650)]
- 父类:
[外链图片转存中…(img-xzVsIRrv-1707740361651)]
- 子类:
- 返回图片类型Type
- 返回这个class到static的变量中