汇编语言
汇编语言是最靠谱的验证“编程语言相关知识点”正确性的方式
汇编语言与机器语言一一对应,每一条机器语言都有与之对应的汇编指令
机器语言是计算机使用的语言,它是一串二进制数字
- 汇编语言可以通过
汇编
得到机器语言 - 机器语言可以通过
反汇编
得到汇编语言
一:new和delete的编译器转化
有一个复数类:class complex { public:
complex(double a,double b); private:
double real; double imag; };
new
则:complex* pc=new complex(1,2);
new得到的内存都是堆内存,需要通过delete释放内存空间
因为程序结束后,指针pc失效了,但是pc指向的内存需要人工回收
编译器对new进行以下的操作:
- 首先进行内存分配
void* mem=operator new(sizeof(complex));
operator new是c++库的一个函数,内部调用c语言使用的malloc
- 其次对指针类型转化
pc=static_cast<complex*>(mem)
把pc的类型从void转换成complex
- 调用构造函数
pc->complex::complex(1,2);
其实构造函数有一个默认参数:*this,这个参数传入的就是 *pc
delete
delete pc;
把上文在堆内存分配的pc申请的内存主动回收
编译器所做的工作:
- 调用析构函数
complex::~complex(pc);
- 释放内存
operator delete(pc)
operator delete函数在内部调用
free()
完成释放内存
总结:
new是先申请内存,再调用构造函数
delete是先调用析构函数,再释放内存
new分配的内存必须是16的倍数,不是需要填充到最近的16倍数
删除数组用delete[]
String* p=new String[3];//指针数组
delete[] p;
如果不加[],delete确实会将整个申请空间释放,但是:
指针对象可能自己申请了空间,这个空间必须靠自己的析构函数去释放
所以创建了指针数组必须对每一个对象都进行内存释放,使用
delete []
去多次调用析构函数
二:复习String类
class String{
public:// 构造函数--最好用inline提高效率inline String(const char *str = 0){if (str == 0){data = new char[1];data[0] = '\0';//*data='\0'}else{data = new char[strlen(str) + 1];strcpy(data, str);//目的,源头}}// 拷贝构造String(const String &str){data = new char[strlen(str.data) + 1];strcpy(data, str.data);}// 赋值构造---返回值本来就存在不,是在函数内创建,用&String &operator=(const String &str){// 不可以自我赋值if (this == &str)//指针地址相同return *this;//赋值是接收端本来就存在,所以需要清除原来内容delete[] data;data = new char[strlen(str.data) + 1];strcpy(data, str.data);return *this;//有返回值可以连串赋值:a=b=c}// 析构函数inline ~String(){delete[] data;}//输入输出辅助函数char *get_data() const { return data; }
private:char *data; // 动态分配字符指针
};
最需要注意的就是赋值构造
- 首先考虑返回值和传入参数:
-
传入参数:类对象的引用
-
返回值:类对象,由于=右边是提前存在的,用&
String &
operator=(const String &str)retutn
*this
;
- 其次注意一定要先释放目标端(=左边)的所有内容然后重新分配空间
delete[] data;data = new char[strlen(str.data) + 1];
- 最后注意自我赋值,也就是a=b中,a和b的字符数组指针不可以相同
if (this == &str)//指针地址相同return *this;
this是指针,&str是str的地址,两者不能相同
//输入输出辅助函数char *get_data() const { return data; }
//定义:String的输出
ostream& operator<<(ostream& os,String s){os << s.get_data();//接收端是字符指针return os;
}
注意:os<<
字符指针
所以需要定义返回字符指针的私有函数,注意不改变该值,所以加上
const
三:拷贝构造函数
浅拷贝
class Rect{
public:Rect() { p = new int(100); }~Rect() {cout << "析构函数" << endl;if(p){delete p;}}void setp(int x) { *p = x; }int getp() const { return *p; }
private:int *p;
};
int main(){Rect a;Rect b(a);b.setp(920);cout << a.getp() << "," << b.getp() << endl;b.setp(320);cout << a.getp() << "," << b.getp() << endl;
}
一个改变,其他也改变,这是因为两个指针指向同一内容,这是浅拷贝Rect a;Rect b(a);a.~Rect();b.getp();
一个对象析构后,其他对象无内容
在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝
深拷贝
class Rect{
public:Rect() { p = new int(100); }Rect(const Rect& r) {p = new int[1];*p = *(r.p);}~Rect() {cout << "析构函数" << endl;if(p){delete p;}}void setp(int x) { *p = x; }int getp() const { return *p; }
private:int *p;
};
int main(){Rect a;Rect b(a);// Rect b=a;a.setp(200);cout << a.getp() << "," << b.getp() << endl;a.~Rect();b.setp(100);cout << b.getp() << endl;
}
p = new int(100);
----赋值100
p = new int[1];
----分配一个内存空间
小结
拷贝有两种:深拷贝,浅拷贝。
- 当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。
当数据成员中没有指针时,浅拷贝是可行的
但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以这时必须采用深拷贝。
四:static
static修饰函数中变量或者类中的变量
当变量被声明为静态时,会在内存的静态变量区
为其分配空间,变量值会存活在程序的生命周期内。
也就是函数内定义的静态变量只会初始化一次,而且每次调用后改变的值会影响下一次的结果
int test(){static int i = 0;//在函数test中定义静态变量ii++;return i;
}
int main(){for (int i = 0; i < 5;i++)cout << test() << " ";return 0;
}
运行结果是:1 2 3 4 5
上一次调用中的变量值将被带到下一个函数调用中
类中的静态变量仅在单独的静态存储中分配空间时初始化一次,类中的静态变量由类对象共享
class demo{
public:static int i;//在demo中定义静态变量static char c;demo(){}
};
/*静态变量初始化必须在类外实现*/
int demo::i=100;
char demo::c = 'a';
类的静态变量为类所有,所以不可以
用构造函数来初始化
或者在类内直接初始化
类的静态变量只能由用户使用
类名::静态变量名
显式初始化必须在类外声明类内静态变量—让编译器知道类中存在静态变量
一个类改变了值,其他类都会受影响
int main(){demo aa;demo bb;aa.c = '*';bb.i = 400;cout <<aa.c<<","<<bb.c<<endl;cout <<aa.i<<","<<bb.i<<endl;cout << demo::i << "," << demo::c << endl;return 0;
}
类中静态变量访问,可以用
对象名.
或者类型::
运行结果:
static修饰的类函数只能访问静态变量
静态成员函数只允许访问类的静态数据成员或其他静态成员函数,不能访问类的非静态数据成员或成员函数。
class demo{
public: demo(){}static void get_Info(){cout << i << "," << c << endl;}
private:static int i;//在demo中定义静态变量static char c;
};
/*静态变量初始化必须在类外实现*/
int demo::i=100;
char demo::c = 'a';
int main(){demo aa;demo bb;aa.get_Info();bb.get_Info();//更建议使用demo::get_Info();return 0;
}
静态函数的访问可以用函数名调用,或者类名作用域调用
static修饰类成员对象
静态类对象:就像变量一样,声明为静态的对象也具有直到程序生命周期为止的作用域
#include <iostream>
#include <cstring>
using namespace std;
class demo{
public:demo(string s):s(s) { cout << "construct fun " <<s<<endl; }~demo() { cout << "delete fun " <<s<< endl; }
private:string s;
};
int main(){int x = 1;if(x==1){static demo bb("bb");}if(x==1){demo aa("aa");}return 0;
}
输出结果:
construct fun bb
construct fun aa
delete fun aa
delete fun bb发现aa对象在{}结束后就调用析构函数,但是static对象bb直到程序结束才调用析构函数
stactic内存
class complex{
public:complex(double re):re(re) {}double real() const { return re; }
private:double re;
};
int main(){complex c1(2.3), c2(3.2), c3(4.6);cout<<c1.real()<<endl; cout<<c2.real()<<endl;cout<<c3.real()<<endl;
}
其实在执行:c.real()
时候:编译器的形式为:
cout<<complex::real(&c1)<<endl; cout<<complex::real(&c2)<<endl;cout<<complex::real(&c3)<<endl;
也就是说编译器默认有一个参数是对象的地址,这个也就是this指针
- 静态数据只有一份所有类公用
- 非静态函数只有一份,但是每个对象都有自己的数据内存,所以只能通过this指针来找到相应数据存储区域
静态函数没有this指针
class Account{ public:static double m_rate;//静态变量定义static void set_rate(const double &x) { m_rate = x; } }; double Account::m_rate;//静态变量声明,可以不初始化 int main(){Account::set_rate(9.8);cout << Account::m_rate << endl; }
静态变量必须在类外有声明
五:单例模式
单例模式具有如下特点:
- 一个类只有一个实例化对象
- 全局可以使用
为了保证类的对象只有一个:
私有的构造函数, 拷贝构造函数,以及
operator=
都必须是类的私有成员建立一个公有函数:
该公有的静态成员函数,用来
构造这个类唯一的实例对象
,并返回
class single{
public://返回静态成员,所有用static,对象存在返回值用&static single& getInstance(){return tar;}void print(){cout << "success!" << endl;}
private://三个构造函数single(){}single(const single& x){}single &operator=(const single &x) { return *this; }//静态类对象static single tar;
};
single single::tar;//静态成员声明
int main(){single::getInstance().print();
}
函数的调用:
single::getInstance()
静态成员必须类外声明一下
class single{ public://...static single& getInstance();//...private://...static single tar; }; /*返回值 类名::静态变量*/ single single::tar;//静态成员声明
这个方式直接创建了类对象,如果不需要使用会造成资源浪费,最好是:使用时创建
静态成员函数内的静态变量可以不类外声明
class single{
public://返回静态成员,所有用static,对象存在返回值用&static single& getInstance();void print(){cout << "ok!" << endl;}
private://三个构造函数single(){}single(const single& x){}single &operator=(const single &x) { return *this; }
};
single& single::getInstance() {//在调用时候创建static single tar;return tar;
};
int main(){single::getInstance().print();
}
上述其实对应类单例模式的两种类型:
根据单例的创建时间,可以分为
饿汉模式
和懒汉模式
。
懒汉模式
: 当你的应用程序在需要调用返回类对象()
方法的时候才创建实例化的对象,类似于懒加载
。
这种方式的好处在于,有一些单例模式的实例,可能在整个进程的声明周期内可能不一定用到。那么这种懒汉模式
就省去了不必要的资源创建过程。
饿汉模式
一般的实现方式为:在进程或者模块加载的时候就会创建全局的实例。
单例模式释放资源
class signlePattern{
public://函数对外接口static signlePattern* getInstance(){/*加上一句判断*/if(point==NULL){point = new signlePattern();}return point;}//析构私有成员函数static void releaseSource(){if(point){delete point;point = NULL;}cout << "finish" << endl;}void test() {cout << "has called!" << endl;}private://三个函数signlePattern(){}signlePattern(const signlePattern& x){}signlePattern &operator=(const signlePattern &x) { return *this; }//静态成员对象--提前创建static signlePattern *point;
};
//静态独享成员声明
//创建对象
signlePattern *signlePattern::point = new signlePattern;
int main(){signlePattern::getInstance()->test();signlePattern::releaseSource();
}
六:模板
类模板
class complex{
public:complex(T r=0,T i=0):re(r),im(i){};T real() const { return re; }T imag() const { return im; }
private:T re, im;
};
int main(){complex<int> c1(3, 6);complex<double> c2(1.2, 3.4);cout << c1.real()<< "," << c1.imag() << endl;cout << c2.real() << "," << c2.imag() << endl;
}
必须在定义时候声明模板类型
complex<int> c1(3, 6);complex<double> c2(1.2, 3.4);
这个参数从编译角度就是把所有T换成声明类型,存在代码浪费
函数模板
class cmp{
public:cmp(int h=0,int w=0):h(h),w(w){};//重载运算符<bool operator<(const cmp& x) const{return h + w < x.h + x.w;}void getInfo(){cout << h << "," << w << endl;}
private:int h,w;
};
//函数模板
template<typename T>
const T& Min(const T& a,const T& b){return a < b ? a : b;
}
int main(){cmp a(3, 2);cmp b(2, 1);cmp c= Min(a, b);c.getInfo();
}
函数模板不需要指定类型,编译器会通过传入参数自动推导
七:面向对象编程
1:复合关系:composition
类中有类—has a
在一个类里内嵌了其他类的对象作为成员的情况,它们之间的关系是一种
包含与被包含
的关系。简单说,一个类中有若干数据成员是其他类的对象。
列子:自定义一个队列类,类中存在一个双端队列成员,然后利用deque的几个函数完成普通队列功能
#include <queue>
using namespace std;
template<class T>
class MYqueue{
private:deque<T> dq;
public:void push(const T &x) { dq.push_back(x); }void pop() { dq.pop_front(); }int size() const { return dq.size(); }T &front() { return dq.front(); }T &back() { return dq.back(); }
};
主函数:
int main(){MYqueue<int> mq;for (int i = 1; i <= 5;i++)mq.push(i*100);cout << mq.size() << endl;cout << mq.front() << "," << mq.back() << endl;mq.pop();cout << mq.front() << "," << mq.back() << endl;mq.pop();cout << mq.front() << "," << mq.back() << endl; }
输出结果:
内存角度
既然是包含关系,那么大的字节数是它包含的类的字节数加上自己的成员
点和距离例子
点类
class point {
public://构造函数point(int x, int y) : x(x), y(y) { cout << "point构造函数" << endl; }//拷贝构造point(const point& tmp) :x(tmp.x), y(tmp.y) {cout << "point拷贝构造函数" << endl;}//返回成员int getX() const { return x; }int getY() const { return y; }
private:int x, y;
};
距离类
class Distance {
public://构造函数Distance(point, point);//返回distdouble getDist() const { return dist; }private:point a, b;double dist;
};
Distance::Distance(point a, point b) :a(a), b(b) {cout << "distance构造函数" << endl;double x = double(a.getX() - b.getX());double y = double(a.getY() - b.getY());dist = sqrt(x * x + y * y);
}
主程序:
int main(){point aa(0, 3);point bb(4, 0);Distance cc(aa, bb);cout << cc.getDist() << endl;
}
运行结果:
Distance(point a, point b) :a(a), b(b) {}
首先两个参数的传入调用了两次拷贝构造函数
然后把参数赋值给类的内部成员进行初始化又调用了两次拷贝构造函数
所以:一共四次调用了拷贝构造函数
还可以调用内联函数的普通函数
Distance(int,int,int,int);//声明point(int x, int y) : x(x), y(y) { cout << "point构造函数" << endl; }
Distance::Distance(int aa,int bb,int cc,int dd) :a(aa,bb), b(cc,dd){cout << "distance构造函数" << endl;double x = double(a.getX() - b.getX());double y = double(a.getY() - b.getY());dist = sqrt(x * x + y * y); }
运行结果:
point构造函数
point构造函数
distance构造函数
三角锥和三角形
class triangle{
public:triangle(int a,int b):a(a),b(b){}triangle(const triangle& x):a(x.a),b(x.b){}double square() const { return (1.0 / 2) * a * b; }
private:int a, b;
};
class triPyramid{
public:triPyramid(int a,int b,int h):t(a,b),h(h){}triPyramid(triangle t,int h):t(t),h(h){}double tiji() const { return (1.0) / 3 * t.square() * h; }private:triangle t;int h;
};
int main(){triangle aa(2, 3);triPyramid m(aa, 6);//triPyramid(triangle t,int h)triPyramid n(2, 4, 6);//triPyramid(int a,int b,int h)cout << aa.square() << endl;cout << m.tiji()<<","<<n.tiji()<<endl;
}
构造函数和析构函数的顺序
class A{};
class B{ private:A a};
-
构造由内而外:(套娃)
B首先调用的是A的默认的构造函数,然后再执行自己的构造函数
-
析构由外而内:(打碎套娃)
B先执行自己,然后再调用A对象的析构函数
class point {
public://构造函数point(int x, int y) : x(x), y(y) { cout << "point构造函数" << endl; }//拷贝构造point(const point& tmp) :x(tmp.x), y(tmp.y) {cout << "point拷贝构造函数" << endl;}//析构函数~point() { cout << "point析构函数" << endl; }
private:int x, y;
};
class Distance {
public:Distance(int aa, int bb, int cc, int dd) :a(aa, bb), b(cc, dd) {cout << "distance构造函数" << endl;}~Distance() {cout << "Distance析构函数" << endl;}
private:point a, b;
};
int main() {Distance cc(0,3,4,0);
}
class A{
public:A(int x) : x(x) { cout << "A构造函数" << endl; }~A() { cout << "A析构函数" << endl; }void show() { cout << x << endl; }
private:int x;
};
class B{
public:B(int a,int b,int c):a(a),b(b),y(c){cout << "B构造函数" << endl;}~B() { cout << "B析构函数" << endl; }A geta() const { return a; }A getb() const { return b; }void show() { cout << y << endl; }private:A a, b;int y;
};
int main(){B x(10, 20, 30);x.geta().show();x.getb().show();x.show();
}
注意:
10
A析构函数
20
A析构函数
这里:
x.geta().show();
等价于:A a(10) ; a.show();
隐藏了声明类A的对象,因为由于包含的原因,类B已经有类A的对象的声明了
A& geta() { return a; } A& getb() { return b; } //----- A* geta() { return &a; } A* getb() { return &b; }B x(10, 20, 30);x.geta()->show();x.getb()->show();
传入引用或者指针就是类B自己的对象了
2:委托 composition by reference
说白了就是:一个类存在另一个类的指针
例子:
- 为狗类设一个主人类的对象指针
- 为主人类设一个狗类的对象指针数组
class CDog; class CMaster // 主人类 {CDog * pDogs[10]; // 狗类的对象指针数组 };class CDog // 狗类 {CMaster * pm; // 主人类的对象指针 };
如果不用指针对象,生成 A 对象的同时也会构造 B 对象。也就是说用指针对象不会提前进行构造
class Car {Engine engine; // 成员对象Wing * wing; // 成员指针对象 };
定义一辆汽车,所有的汽车都有 engine,但不一定都有 wing,所以wing 只占一个指针
class handleImpl{ public:void notify(){cout <<"hello"<< endl;} }; class handle{ public:void notify(){p->notify();} private:handleImpl *p; }; int main(){handle *myhandle = new handle();myhandle->notify();return 0; }
handle类包含handleImpl类的指针,handle类的notify()只是一个接口,真正的实现是:handleImpl类的notify()
void notify(){ p->notify(); }
类对象和类指针
- 类对象:
类名 a;
在定义之后就已经为a这个对象分配了内存,且为内存栈,是个局部的临时变量 - 类指针:
类名 *b = new 类名();
在定义*b的时候并没有分配内存,只有执行new后才会分配内存,且为内存堆,是个永久变量,除非你释放它。
3:继承Inheritance
is a关系
子类继承父类的成员还有自己的新成员
继承最有价值的部分是"
虚函数
"
-
构造函数
由内而外(先子类后父类)先调用父类的默认构造函数,然后调用子类成员的构造函数。
Derived::Derived(...):Base(..){ ...}
-
析构函数
由外而内(先父类再子类)先调用子类成员的析构函数 ,最后调用父类的构造函数。
Derived::~Derived() { ..., ~Base(); }
注意----析构函数要想保证先子类析构,后父类析构,那么:父类的析构函数必须是virtual
例子:
class doucument{
public:doucument(const char *s){name = new char[strlen(s) + 1];strcpy(name, s);}doucument(const doucument &s){name = new char[strlen(s.name) + 1];strcpy(name, s.name);}void showName() { cout << name << endl; }
private:char *name;
};
class Book:public doucument{
public:Book(const char* name,double p):doucument(name),price(p){}Book(const doucument& x,double p):doucument(x),price(p){}void show() {showName();cout << price<< endl; }
private:double price;
};
int main()
{doucument mm("hello");Book n1("good", 19.9);Book n2(mm, 29.9);n1.show();n1.show();
}
Book(const char* name,double p):doucument(name),price(p){}
Book(const doucument& x,double p):doucument(x),price(p){}
注意:基类的构造函数名称要指明
基类的共有函数可以被派生类直接调用
void show() {
showName();
cout << price<< endl; }
4:虚函数
- 非虚函数(non-virtual)
不希望派生类(子类)去重新定义父类
子类重新定义父类的术语:
override
- 虚函数(virtual)
希望子类重新定义父类,而且子类已经有该函数的默认定义
- 纯虚函数(pure virtual)
希望子类
一定
要重新定义父类,因为父类不存在默认定义
class shape{
public://纯虚函数//因为子类形状对父类不可知,所以父类无法预设,只能子类自己实现virtual void drow() const=0;//virtual =0//虚函数//设计成虚函数是,如果子类想要打印更细致的错误信息,子类可以自己实现 virtual void error(const string& msg);//非虚函数//因为不管子类是什么(三角形,矩形),给对象生成编号的动作是一致的,父类可以实现int objectID() const;
};
打开文件
父类提供打开文件的函数,对所有文件来说:查找,搜索,关闭这些动作一样,只有:读取文件内容不同
所以:父类打开文件函数提供一个虚函数读取文件,这个函数由子类实现
如图:子类调用父类的函数,然后父类函数运行到虚函数后,又会去调用子类的函数
简单实现:
class cDocument{
public:void onFileOpen(){cout << "check file status!" << endl;cout << "open file !" << endl;serialize();//子类重载虚函数cout << "close file !" << endl;cout << "update file view!" << endl;}virtual void serialize() =0;
};
class cMydoc:public cDocument{
public:void serialize(){cout << "my file" << endl;}
};
int main(){cMydoc mydoc;mydoc.onFileOpen();
}
八:复合和继承同时出现
有两种:
-
父类存在其他类,子类继承了父类
-
子类继承了父类,子类存在其他类
子类继承父类,存在其他类对象
//假设类B继承类A,类B中有类C对象
class A{
public:A(int a):a(a) {cout << "构造函数A()" << endl;}~A() { cout << "析构函数~A()"<<endl; }void getA() { cout <<"A::"<<a << endl; }
private:int a;
};
class C{
public:C(char ch):ch(ch){cout << "构造函数C()" << endl;}~C() { cout << "析构函数~C()" << endl; }void getC() { cout <<"C::"<<ch << endl; }
private:char ch;
};
class B:public A{
public:B(int a,char ch,double x):A(a),obj(ch),x(x) {cout << "构造函数B()" << endl;}B(char ch,int a,double x):obj(ch),A(a),x(x) {cout << "构造函数B()" << endl;}~B() { cout << "析构函数~B()" << endl; }C getOBJ() const { return obj; }void getB() { cout <<"B::"<<x << endl; }
private:C obj;double x;
};
运行结果:
int main(){B a(300,'A',19.87);B b('B', 900, 19.87); }
构造顺序是首先调用继承的父类,其次调用组合中的实例对象,最后才是构造自己本身
B a(300,‘A’,19.87);
a.getA();
a.getB();
a.getOBJ().getC();
父类存在其他类,子类继承
//类B中有类C对象,类A继承类B
class C{
public:C(char ch):ch(ch){cout << "构造函数C()" << endl;}~C() { cout << "析构函数~C()" << endl; }void getC() { cout <<"C::"<<ch << endl; }
private:char ch;
};
class B{
public:B(char ch,double x):obj(ch),x(x) {cout << "构造函数B()" << endl;}~B() { cout << "析构函数~B()" << endl; }C getOBJ() const { return obj; }void getB() { cout <<"B::"<<x << endl; }
private:C obj;double x;
};
class A:public B{
public:A(int a,char c,double b):a(a),B(c,b){cout << "构造函数A()" << endl;}~A() { cout << "析构函数~A()"<<endl; }void getA() { cout <<"A::"<<a << endl; }
private:int a;
};
int main(){A a(100, 'a', 99.89);a.getA();a.getB();a.getOBJ().getC(); }
这个很简单:
类中有复合:先复合构造再自己
类中有继承:先继承构造再自己
类A中有继承B,继承B中有复合C:先继承B(先复合C再自己B)再自己A
CBA
析构先自己,再复合或构造