C++面向对象高级开发

一、C++编程介绍

你应具备哪些基础

·曾经学过某种语言(procedural language) C语言最佳
·变量(variable)
·类型(types):int,float,char,struct…
·作用域(scope)
·循环(loops):while,for
·流程控制:if-else,switch-case

·知道一个程序需要编译、链接才能被执行

·知道如何编译和链接
(如何建立一个可执行程序)

我们的目标

·培养正规的、大气的编程习惯

·以良好的方式编写C++ class Object Based(基于对象)

​ · class without pointer members

​ -Complex

​ ·class with pointer members

​ -String

·学习classes之间的关系 Object Oriented(面向对象)

​ -继承(inheritance)

​ -复数(composition)

​ -委托(delegation)

你将获得的代码

complex.h complex-test.cpp

string.h string-test.cpp

oop-demo.h oop-test.cpp

C++的历史

·B语言(1969)

·C语言(1972)

·C++语言(1983)
(new C -> C with Class -> C++)

·Java语言

·C#语言

C++演化

·C++ 98(1.0)
·C++ 03(TR1)
·C++11(2.0)
·C++ 14

C++由C++和C++标准库组成

二、头文件与类的声明

对于数据和函数

函数是用来处理数据的

C语言中,数据是全局的,会造成麻烦

C++中,数据和函数被包裹在一起,在一起的函数和数据,该函数只能控制该数据,体现了"封装的思想"

(img-GceAvIHh-1642600254784)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220112193714678.png)]

字符串内中,其实含有一个指针,通过new的方法来申请内存空间(创建对象)。

在这里插入图片描述

Object Based(基于对象)vs Object Oriented(面向对象)

Object Based:面对的是单一 class 的设计

Object Oriented:面对的是多重classes的设计,classes和classes之间的关系。

我们的第一个C++程序

Classes的两个经典分类:

·Class without pointer member(s)
complex

·Class with pointer member(s)
string

C++ programs 代码基本形式

头文件+主程序

include(包含)自己写的,需要使用"",包含标准库,需要使用<>。

在这里插入图片描述

延展文件名(extension file name)不一定是.或.cpp,也可能是.hpp或其他或甚至无延伸名。

Output,C++ vs C

在这里插入图片描述

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

防卫式声明是一种判断的声明,若COMPLEX头文件被include了,则不会再include。若没有,则被include。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99myIAdg-1642600254786)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220112204442161.png)]

//complex-test.h
#include<iostream>
#include"complex.h"
using namespace std;

int main()
{
    complex c1(2,1);
    complex c2;
    cout << c1 << endl;
    cout << c2 << endl;
    
    c2 = c1 + 5;
    c2 = 7 + c1;
    c2 = c1 + c2;
    c2 += c1;
    c2 += 3;
    c2 = -c1;
    
    cout << (c1 == c2) << endl;
    cout << (c1 != c2) << endl;
    cout << conj(c1) << endl;
    return 0;
}

Header(头文件)的布局

#ifndef _COMPLEX_
#define _COMPLEX_
1.//forward declarations(前置声明)
#include<cmath>

class ostream;
class complex;

complex& _doapl(complex* ths,const complex& r);
2.//class declarations(类-声明)
class complex
{
    ...
};
3.//class definition(类-定义)
complex::function ....
#endif1

class的声明(declaration)

class complex 				//class head
{							//class body
 public:
    complex (double r = 0,double i = 0): re (r), im (i) {}
    //有些函数在此直接定义,另一些在body之外定义
    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);
    complex c2;
    ...
}

class template(模板)简介

模板的出现原因是为了减少代码的重复性

template<typename T>
class complex 				//class head
{							//class body
 public:
    complex (double r = 0,double i = 0): re (r), im (i) {}
    //有些函数在此直接定义,另一些在body之外定义
    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);
    //模板上的T就会变成double
    complex<int> c2(2,6);
    ...
}

inline(内部)函数

class complex 				
{							
 public:
    complex (double r = 0,double i = 0): re (r), im (i) {}
    complex& operator += (const complex&);
    /*函数若在class body内定义完成,便自动成为inline候选人
    { return re; } { return im; } */
    double real() const { return re; }
    double imag() const { return im; }
 private:
    double re,im;
  	friend complex&  _doapl (complex*,const complex&);
};
inline double
imag(const complex& x)
{
    return x.imag();
}

三、构造函数

access level(访问级别)

对于数据要进行封装操作,访问修饰符使用private。

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.re;
    cout << c1.im;
}
//错误
{
	complex c1(2,1);
    cout << c1.real();
    cout << c1.imag();
}
//正确

constructor(ctor,构造函数)

class有一个经典的分类:带指针的和不带指针的,不带指针的类,多半不用写析构函数。

构造函数的函数名与类名相同,它可以拥有参数,无返回类型。

在创建对象时,构造函数自然而然地就会被调用。

class complex 				
{							
 public:
    /*(double r = 0, double i = 0) default argument(默认实参)
    re (r), im (i) initialization list (初值列,初始列)
    complex (double r = 0, double i = 0){ re = r; im = i ;}*/
    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);
    complex c2;
    complex* p = new complex(4);
    ...
}

ctor(构造函数)可以有很多个 - overloading(重载)

重载:函数名相同,参数不同。

class complex 				1.
{							
 public:	
    /*1*/complex (double r = 0,double i = 0): re (r), im (i) {}
    /*2*/complex () : re(0), im(0) { }
    complex& operator += (const complex&);
    /*1*/double real() const { return re; }
    double imag() const { return im; }
 private:
    double re,im;
  	friend complex&  _doapl (complex*,const complex&);
};
/*2*/void real (double r) { re = r; }
real函数编译后的实际名称可能是:
?real@Complex@@QBENXZ
?real@Complex@@QAENABN@Z
取决于编译器
{
    complex c1;
    complex c2();
    ...
}

constructor(ctor,构造函数)被放在private内

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);
    complex c2;
    ...
}//错误

ctors放在private内

class A{		//Singleton 单一,单件,一份
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(常量成员函数)

const 放在函数名的后面,函数体的前面。(即小括号的后面,大括号的前面); 也可以出现在对象变量)的前面(意思是我的这个对象或者变量一定是不动的,不可以改)

class(类)中的函数又分为两种,一种是可以改变对象的数据,一种是不会改变对象的数据。

不改变数据的函数加上 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();
    cout << c1.imag();
}

四、参数传递与返回值

参数传递:pass by value vs. pass by reference (to 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&);
};
ostream&
operator << (ostream& os, const complex& x)
{
    return os << '(' << real (x) << ','
         	  << imag (x) << ')';
}
{
    complex c1(2,1);
    complex c2;
    
    c2 += c1;
    cout << c2;
}

返回值转递: return by value vs. return by reference (to 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&);
};
ostream&
operator << (ostream& os, const complex& x)
{
    return os << '(' << real (x) << ','
         	  << imag (x) << ')';
}
{
    complex c1 (2,1);
    complex c2;
    
    cout << c1;
    cout << c2 << c1;
}

friend(友元)

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&);
};
inline complex& _doapl (complex* ths,const complex& r)
{
    //自由取得friend的private成员
    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)
    { }
    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 外的各种定义(definitions)

什么情况下可以 pass by reference

什么情况下可以 return by reference

(复习:拿到一个类,注意:第一,数据放到private (封装)第二,参数尽可能地以reference(引用)来传 要不要加 const 看情况 第三,返回值也尽量用 reference 来传)

尽量使用传引用的方式才传递参数,因为引用的底层是指针(C语言中指针作为参数传递类似),传递的数据大小为4个字节,速度会很快。同样,值的返回也尽量返回引用(如果可以的话)。

  1. 数据放在private
  2. 参数用reference,是否用const
  3. 在类的body里的函数是否加const
  4. 构造函数的 initial list
  5. return by reference,不能为local object.
指针&引用:
  1. 都是地址的概念;指针是一个实体,指向一块内存,它的内容是所指内存的地址;引用则是某块内存的别名。
  2. 使用sizeof指针本身大小一般是(4),而引用则是被引用对象的大小;
  3. 引用不能为空,指针可以为空;指针可以被初始化为NULL,而引用必须被初始化且必须是一个已有对象的引用,之后不可变;指针可变;引用“从一而终”,指针可以“见异思迁”;
  4. 作为参数传递时,指针需要被解引用才可以对对象进行操作,而直接对引用的修改都会改变引用所指向的对象;
  5. 引用没有const,指针有constconst的指针不可变;
    具体指没有int& const a这种形式,而const int& a是有的,前者指引用本身即别名不可以改变,这是当然的,所以不需要这种形式,后者指引用所指的值不可以改变)
  6. 指针在使用中可以指向其它对象,但是引用只能是一个对象的引用,不能 被改变;
  7. 指针可以有多级指针(**p),而引用至于一级;
  8. 指针和引用使用自增(++)运算符的意义不一样;
  9. 如果返回动态内存分配的对象或者内存,必须使用指针,引用可能引起内存泄露。
  10. 引用是类型安全的,而指针不是 (引用比指针多了类型检查)
const作用

当一个方法不会对数据进行修改时,尽量将方法指定为const。细分顶层const、底层const

"effective c++"第三条讲到: 只需要判断const是在 * 的左边还是右边即可。左边则是修饰被指物,即被指物是常量,不可以修改它的值;右边则是修饰指针,即指针是常量,不可以修改它的指向;在左右两边,则被指物和指针都是常量,都不可以修改。

  1. 修饰变量,说明该变量不可以被改变;
  2. 修饰指针,分为指向常量的指针和指针常量;
  3. 常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;
  4. 修饰成员函数,说明该成员函数内不能修改成员变量。

do assignment plus

inline complex& _doapl(complex* ths, const complex& r)
{
    ths->re += r.re;
    ths->im += r.im;
    return *ths;
}

inline complex& complex::operator += (const complex& r)
{
    return _doapl (this,r);
}

五、操作符重载与临时对象

在C语言中对一个数据进行操作设计一个函数。实际上在C++中操作符就是一种函数,可以重新定义(最大的特点)

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::operator += (const complex& r)  
{
    return _doapl (this, r);
}
{
    complex c1(2,1);
    complex c2(5);
    
    c2 += c1;
}
//this代表c2,r代表c1

inline complex&
complex::operator += (this,const complex& r)
{
    return _doapl (this,r);
}

return by reference 语法分析

传递者无需知道接收者是以reference形式接收

inline complex&
 _doapl (complex* ths,const complex& r)
{
    ...
    return *ths;
}

inline complex&
complex::operator += (const complex& r)
{
    return _doapl(this,r);
}
{
    complex c1 (2,1);
    complex c2 (5);
    
    c2 += c1;
}

c3 += c2 += c1; //加操作的返回类型不可以是

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

为了对付client(客户)的三种可能用法,这儿对应开发三个函数

inline complex
operator + (const complex& x, const complex& y)
{
    return complex (real (x) + real (y), 
                    img (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));
}
{
    complex c1(2,1);
    complex c2;
    
    c2 = c1 + c2; //调用第一个函数
    c2 = c1 + 5;  //调用第二个函数
    c2 = 7 + c1;  //调用第三个函数
}

temp object(临时对象)typename ();

下面这些函数绝不可 return by reference,因为,它们返回的必定是个local object。

typename (),用于创建temp object(临时对象)。

{
    int (7);
    
    complex c1 (2,1);
    complex c2;
    complex ();
    complex (4,5);
    
    cout << complex (2);
}

class body 之外的各种的定义(definitions)

inline complex 
operator + (const complex& x)
{
    return x;
}
/*negate 反向(取反)
这个函数绝不可return by reference,
因为其返回的必定是个local object。
*/
inline complex
complex - (const complex& x)
{
    return complex (-real (x), -imag (x)); //创建临时对象
}
{
    complex c1 (2,1);
    complex c2;
    cout << -c1;
    cout << +c1;
}

operator overloading(操作符重载),非成员函数

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

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

inline bool
operator == (double x, const complex& y)
{
    return x == real (y) && imag (y) == 0;
}
{
    complex c1 (2,1);
    complex c2;
    
    cout << (c1 == c2);
    cout << (c1 == 2);
    cout << (0 == c2);
}

思考函数的返回值是value还是reference?

operator overloading(操作符重载),非成员函数

inline bool
operator != (const complex& x,
             const complex& y)
{
    return real (x) != real (y)
        || imag (x) != imag (y);
}

inline bool 
operator != (const complex& x, double y)
{
    return real (x) != y || imag (x) != 0;
}

inline bool
operator != (double x, const complex& y)
{
    return x != real (y) || imag (y) != 0;
}
{
    complex c1 (2,1);
    complex c2;
    
    cout << (c1 != c2);
    cout << (c1 != 2);
    cout << (0 != c2);
}

operator overloading(操作符重载),非成员函数

inline complex
conj (const complex& x)
{
    return complex (real (x), -imag (x));
}
#include<iostream.h>
ostream& operator << (ostream& os, const complex& x)
{
    return os << '('<< real (x) << ','
        	  << imag (x) << ')';
}
{
    complex c1 (2,1);
    cout << conj (c1);			//(2,-1)
    cout << c1 << conj (c1);	//(2,1) (2,-1)
}

函数的分类:全局函数(非成员函数),和成员函数

操作符有两种写法:一种是成员函数的写法,一种是非成员函数的写法。
在这里插入图片描述

返回类型改为void可以么?只要不连续输出就可以。

六、复习Complex类的实现过程

总结: 设计一个类,构造函数的形式
    complex (double r = 0, double i = 0)
     : re (r), im (i)
     { }
函数该不该加const。参数的传递是传值value还是引用reference
    数据放在private,函数放在public
二、三、四、五部分的代码
#ifndef _COMPLEX_
#define _COMPLEX_

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&);
};

inline complex&
_doapl (complex& ths,const complex& r)
{
	ths->re += r.real;
	ths->im += r.im;
	return *ths;
}

inline complex& 
complex::operator += (const complex& r)
{
	return _doapl (this,r);
}

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,double y)
{
	return complex ( real (x) +y,imag (x));
}

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

#include<iostream.h>
ostream&
operator << (ostream& os, const complex& x)
{
	return os << '(' << real (x) << imag (x) << ')'; 
}
			 
#endif

complex c1 (9,8);
cout << c1;
c1 << cout;	 //可以这么设计,但是使用者不会这样使用;
cout << c1 << endl;

七、三大函数:拷贝构造,拷贝复制,析构

Classes的两个经典分类

·Class without pointer member(s)

​ complex

·Class with pointer member(s)

​ string

String class

#ifndef _MYSTRING_			string.h
#define _MYSTRING_

class String
{
    ....
};

String::function(...) ...
Global-function(...) ...
    
#endif    
int main()					string-test
{
    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_}
private:
    char* m_data;
};

一般设计字符串时,里面有一个指针,在需要内存的时候,才创建另外一个空间去存放字符本身,是因为字符本身有大有小,所以采用动态分配的方式。

在这里插入图片描述

从C语言以来延续的概念,字符串是什么?有一个指针指着头,后面就一串,最后会有一个0,这样的结束符

ctor和dtor(构造函数 和 析构函数 )

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ABvirnuh-1642600254787)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220116102103913.png)]

class with pointer members 必须有 copy ctor (拷贝构造)和 copy op = (拷贝复制)

在这里插入图片描述

这个是浅拷贝,字符串a,b都含有指针,a,b中的指针指向内存中存放Hello的地址,而内存存放World的地址就会消失。

copy ctor(拷贝构造函数)

inline 
String::String (const String& str)
{
    m_data = new char[ strlen(str.m_data) + 1 ];
    strcpy (m_data, str.m_data);
}
//深拷贝
//str.m_data 直接取另一个object的private
{
    String s1 ("hello ");
    String s2 (s1);
 // String s2 = s1;  
}

copy assignment operator(拷贝赋值函数)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zDoBfXvN-1642600254788)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220116184521251.png)]

{
    String s1 ("hello ");
    String s2 (s1);
    s2 = s1;
}
//s2 = s1,调用的就是拷贝复制函数。this 就是s2。 
if (this == &str)
    return *this
//检测自我赋值 (self assignment)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lODhBkYE-1642600254789)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220116190116947.png)]

浅拷贝——>拷贝构造函数 ,浅拷贝的影响:
1、造成内存泄漏;
2、造成有两个指针 指向同一块内存

深拷贝——>拷贝赋值函数
步骤:delete ;new; strcpy;
class里面有默认的拷贝构造和拷贝赋值函数。如果自己不定义一个拷贝构造函数,在调用拷贝构造函数的时候,就会调用默认的浅拷贝构造函数,就会造成问题,所以一定要自己定义拷贝构造函数——深拷贝。

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)。

class Complex { ... };
...
{
    Complex c1 (1,2);
    Complex* p = new Complex(3);
}
c1 所占用的空间来自stack
Complex(3)是个临时对象,其所占用的仍是以 new 自heap动态分配而得,并由p指向
c1 会因函数的结束,会自动释放内存资源。
而Complex(3)是动态分配的内存(new),函数结束,Complex(3)的内存资源依然存在,需要手动清除资源空间(free)

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 object 的生命期

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 p)

new:先分配memory(内存),再调用ctor(构造函数)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OaCIOvrp-1642600254790)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220117095725955.png)]

谁调用Complex构造函数,谁就是this

pc是m_real和m_imag在内存空间中的首地址

delete:先调用dtor(析构函数),再释放memory(内存空间)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tzLVbsLk-1642600254791)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220117203531508.png)]

动态分配所得的内存块(memory block),in VC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9LHtQiNN-1642600254791)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220117210211889.png)]

在调试模式底下,会多灰色的内存空间(一个小矩形是4个字节,一共是32+4个字节)再加上砖红色内存空间(4*2)(叫cookie作用:记录整块内存空间的大小,为什么要记录内存空间的大小呢?因为将来系统回收时,只给它一个指针,不知道回收多大的内存空间,所以必须有一个地方记录这个长度,malloc和free规定在最上面是申请的内存空间大小)内存空间总共:8+(32+4)+(4×2)

结果是64 用十六进制表示应该是0x40,而结果是0x41,借用最后一个bite位,0/1来表现这块内存空间是给出去还是收回来,对于操作系统是给出去了,对于程序而言是获得了所以最后一位是1。为什么可以通过查看最后一位是0还是1来判断内存空间是申请还是释放?因为申请空间是16的倍数最后4个bite都是0,所以可以借其中一位来使用。

不进入调试模式:就没有灰色的内存空间,则内存空间总共:8+(4×2)

对于字符串而且,字符串内含一个指针,所以字符串申请内存空间时,是申请存放指针大小的内存空间,所以是4个字节

在非调试模式下没有灰色的内存空间,4个字节+上下cookie,结果是12个字节,但是申请内存空间必须是16的倍数,所以会调整到16的倍数。

动态分配所得的是array

注意:array new,搭配array delete

m_data = new char [ strlen(cstr)+1 ];
delete [] m_data;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-g1J6kJPT-1642600254791)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220118083830895.png)]

进入调试模式,会申请橘黄色的内存空间。

不进入调试模式,不会申请橘黄色的内存空间。

字符串内含有一个指针。一个指针占4个字节。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y5unL2HI-1642600254792)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220118084325725.png)]

清空内存空间要查看cookie

写成delete [ ], 告诉编译器删除的是一个数组,编译器才知道原来下面有三个,这才调用三次析构函数(每一个对象在死亡之前,都要调用析构函数)。这三个析构函数就各自负责把各自的动态分配的内存杀掉。

如果不写 [ ],编译器以为下面只有一个所以调用一次析构函数而已。

对于class对象使用delete,而不是delete [ ],不会造成影响。

而对于字符串而言,使用delete,而不是delete [ ],会造成影响。

记住一点:使用 array new 就使用 array delete。

九、复习String类的实现过程

注意使用防卫式声明

设计一个class 总先思考使用什么样的数据,字符串里会放很多字符,里面放一个数组,里面都是字符,但是这种想法不好,因为需要指明该数组的大小。但是设置太大或者太小的数组都不好。

让里面放一个指针,将来要放多大的字符串的内容,就动态分配这个大小。用 new 的方式(对于C语言就是使用malloc的方式)在32位的电脑里面,一个指针是4个字节,不管字符串里面放的是hello world…,字符串对象本身就是4个字节。

Class String
{
public:
    //构造函数没有返回值,接收一个指针指向字符(就是字符串),这种形式是传统的C语言形式字符串。传进来当然不会改变传进来的值,拿来当初值而已,所以加const,并且给一个默认值0。
    String (const char* cstr = 0);
    //拷贝构造
    String (const String& str);//str是要拷贝构造自己
    //拷贝赋值,把来源端拷贝到目的端,并不打算改它,所以加const 得到的结果就是目的端这种东西。要不要return by reference 返回引用? 这个函数执行的结果,放到什么地方去,放到的地方是不是local object,只要不是local object 就可以传reference,从来源端赋值到目的端,这个目的端本身就存在,不需要在这个拷贝赋值函数中创建。
    String& operator=(const String&)
    //析构函数
    ~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;
}

copy ctor(拷贝构造函数)

inline
String::String (const String& str)
{
    m_data = new char[ strlen(str.m_data)+1];
    strcpy(m_data,str.m_data);
}    

copy assignment operator(拷贝赋值函数)

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;
}

十、扩展补充:类模块,函数模板,及其他

进一步补充:static

static静态

在class里面数据或者函数前面+static,就会成为静态的数据或者静态的函数

静态数据和静态函数,和对象就脱离了。单独有一份

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8mv4rKOf-1642600254792)(C:\Users\lenovo\AppData\Roaming\Typora\typora-user-images\image-20220118204425158.png)]

c1的地址 &c1,c2的地址&c2 就是this pointer,通过不同的地址处理不同的数据

中间灰色的部分是在复数类中创建的对象(非静态成员)。

右上3个灰色图片是在内存中的信息,中间的是成员函数。

成员函数只有一份(中间的那个)但是他要处理很多个对象,一定要有一个数据告诉它你要处理谁,靠得就是这个this pointer

最下面一行表示的是this pointer 传递到成员函数中的过程

注意:成员函数有一个隐藏的参数----this pointer

静态的数据只有一份(多个对象共同使用)含有this pointer

什么时候使用静态的函数呢?

静态函数和静态数据的区别就是没有this pointer,不能像一般的成员去取,去访问,去处理c1,c2,c3,这些对象中的的数据。若要处理数据,只能处理静态数据。

class Account{
public:
    static double m_rate;
    static void set_rate(const double& x) { m_rate = x };
};
//如果是静态的数据,一定要在class外做初始化。
double Account::m_rate = 8.0;  //定义

int main(){
    Account::set_rate(5.0);
    
    Account a;
    a.set_rate(7.0);
}
//调用static函数的方式有二:
(1)通过object(对象)调用
(2)通过class name(类名)调用

进一步补充:把ctors放在private内

//singleton
class A{
public:
    //该函数是对外界的唯一窗口,外界通过这个函数使用类中唯一一个对象,a。
    static A& getInstance( return a; );
    setup() { ... }
private:
    A();
    A(const a& rhs);
    static A a;
    ...
};
A::getInstance().setup();

不想让用户创建该类的对象,把构造函数放private内。该类只有一个对象。缺点:如果外界没有使用到对象a,但是这个静态a仍然存在,有点浪费内存空间。

//Meyers 
class A{
public:
    static A& getInstance( return a; );
    setup() { ... }
private:
    A();
    A(const A& rhs);
    ...
};

A& A::getInstance()
{
    static A a;
    return a;
}

A::getInstance().setup();
//调用getInstance()该函数,才会创建该类对象。

进一步补充: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,函数模板

stone r1(2,3),r2(3,3),r3;
r3 = min(r1,r2);
//编译器会对function template 进行
//引导推论(argument deduction)
template <class T>
inline
const T& min(const T& a,const T& b)
{
    return b < a ? b : a;
}
//引导推理的结果,T为stone,于是调用 stone::operator<
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;
};

进一步补充:namespace

//std:标准库中所有的东西都被包再std
namespace std
{
    ...
}
//using directive
#include <iostream.h>
using namespace std;//使用std这个命名空间

int main()
{
    cin << ...;
    cout << ...;
    
    return 0;
}
//using declaration
#include <iostream.h>
using std::cout;

int main()
{
    std::cin << ...;
    cout << ...;
    
    return 0;
}

更多细节深入

· operator type() const;
· explicit complex(...):initialization list{}
· pointer-like object
· function-like object
· Namespace
· template specialization
· Standard Library
· variadic template
· move ctor
· Rvalue reference
· auto 
· lambda
· range-base for loop
· unordered containers
 ....
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值