候捷课1—面向对象高级开发
文章目录
- 候捷课1—面向对象高级开发
- 1.头文件
- 2.构造函数
- 3.常量成员
- 4.参数传递
- 5.返回值传递
- 6.同个class中的object互为friend
- 7.什么时候不能返回引用
- 8.typename()临时对象
- 9.重载操作符
- 10.拷贝
- 11.栈与堆
- 12.继承
- 13.虚函数
- 14.prototype
- 15.转换函数
- 16.智能指针
- 17.仿函数
- 18.namespace
- 19.模板
- 20.模板特化
- 21.模板偏特化
- 22.模板参数
- 23.可变数目模板参数
- 24.auto
- 25.reference
- 26.继承和组合都存在时,构造与析构情况
- 27.虚指针与虚函数表
- 28.this指针
- 29.const
- 30.重载new和delete以及new[] delete[]
- 31.重载new() delete()
1.头文件
xxxx.h
#ifndef _XXXX_
#define _XXXX_
.
.
.
#endif
2.构造函数
利用初始值,初始列表构造
complax(double r=0,double i=0):re®,im(i)//初始化列表
{}
//如果在{}中写re=r,im=i;这种属于赋值 //会出现问题
//第二种构造函数重载不行,因为会产生歧义,与第一个重复构造
3.常量成员
如果只读不写就加const ,能加就加
4.参数传递
参数传递最好用引用(特殊情况传字符等,字符不加引用传递快)
如果不想修改传递值就加const
5.返回值传递
最好也要加&,但是不是所有情况都可以加
6.同个class中的object互为friend
7.什么时候不能返回引用
所谓的不可以返回局部变量的引用或指针,指的是不能返回局部变量的引用或地址给引用或指针。事实上还是看该地址的位置是否在该函数的栈区,若是在栈区,函数调用结束,该地址就被释放了。尽管会出现栈地址上的值没被销毁的问题,可能是该栈区还没被其他的函数堆栈掉
8.typename()临时对象
9.重载操作符
1.输入输出操作符最好设置在全局函数(<<与>>)
如果设置为成员函数,写法会与平常写法相反
// cout重载能不能写成成员函数,若能,写出函数原型,若不能,说明原因
#include <iostream>
using namespace std;
// cout做友元
class A;
ostream& operator<<(ostream &out, const A &a);
class A
{
friend ostream& operator<<(ostream &out, const A &a);
public:
A(int n = 0):m(n)
{}
private:
int m;
};
ostream& operator<<(ostream &out, const A &a)
{
out << a.m;
return out;
}
int main()
{
A a;
cout << a << endl;
return 0;
}
// cout并不是不可以做成员函数,但是它的输出形式并不符合大家的习惯,所以还是不要把它写到成员函数里比较好
#include <iostream>
using namespace std;
class A
{
public:
A(int n = 0) :m(n)
{}
// cout做成员函数
ostream& operator<<(ostream &out)
{
out << m;
return out;
}
private:
int m;
};
int main()
{
A a;
//注意输出格式
a << cout << endl;
return 0;
}
2.输出操作符<<重载不能加const,因为输出每时每刻都在改变
10.拷贝
1.拷贝构造:有指针情况要进行深拷贝
2.拷贝赋值:需要加一个检测自我赋值
11.栈与堆
12.继承
13.虚函数
14.prototype
如果一个父类很早就创造出来了但是想要创建未来子类对象时(通俗讲就是为了让父类学习知道未来子类的东西),他的子类可以自己创建自己,加上addPrototype(this);可以把子类原型放入父类创建的prototype的空间,加上clone();目的是return new 子类,就是说让父类看到子类的原型,然后通过原型调用副本。父类中有findandclone可以找到子类去复制子类副本,但是子类通过return new子类重新构造是调用另外一个#landsatimage(int)这个构造函数,防止重复上面的流程。
15.转换函数
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den)
{ cout << m_numerator << ' ' << m_denominator << endl; }
operator double() const {
return (double)m_numerator / m_denominator;
}//转换函数
private:
int m_numerator; //
int m_denominator; //
};
void test_conversion_functions()
{
cout << "test_conversion_functions()" << endl;
Fraction f(3,5);
double d = 4 + f; //4.6
cout << d << endl;
}
任何Fraction需要被转换为double类型的时候,自动调用double()函数进行转换。编译器在分析double d = 4 + f过程中判断4为整数,然后继续判断f,观察到f提供了double()函数,然后会对f进行double()操作,计算得到0.6,再与4相加,最后得到double类型的4.6。
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den)
{ cout << m_numerator << ' ' << m_denominator << endl; }
Fraction operator+(const Fraction& f) {
cout << "operator+(): " << f.m_numerator << ' ' << f.m_denominator << endl;
return f;
}
private:
int m_numerator; //
int m_denominator; //
};
void test_conversion_functions()
{
cout << "test_conversion_functions()" << endl;
Fraction f(3,5);
Fraction d2 = f + 4;
}
定义了一个类,叫Fraction,类里面重载了“+”运算符,在f+4操作过程中,“4”被编译器隐式转换(构造函数)为Fraction对象,然后通过Fraction重载的“+”运算符参与运算。
class Fraction
{
public:
Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den)
{ cout << m_numerator << ' ' << m_denominator << endl; }
operator double() const {
return (double)m_numerator / m_denominator;
}
Fraction operator+(const Fraction& f) {
cout << "operator+(): " << f.m_numerator << ' ' << f.m_denominator << endl;
return f;
}
private:
int m_numerator;
int m_denominator;
};
void test_conversion_functions()
{
cout << "test_conversion_functions()" << endl;
Fraction f(3,5);
double d = 4 + f; //4.6
cout << d << endl;
//! Fraction d2 = f + 4; //ambiguous
//会有歧义报错
}
在Fraction中增加了double()函数,将Fraction的两个成员变量进行除法运算,然后强制转化为double类型并返回结果,在f+4重载过程中编译器将报错,可以做出如下分析:
1、首先4被隐式转化(构造函数)为Fraction对象,然后通过重载的“+”运算符与“f”进行运算返回一个Frction对象;
2、首先4被隐式转化(构造函数)为Fraction对象,然后通过重载的“+”运算符与“f”运算后对进行double运算,最返回一个Frction对象;
3、。。。
所以编译器有至少两条路可以走,于是产生了二义性,报错。在构造函数Franction前加入explict关键字,隐式转换将取消,所以在执行d2 = f + 4过程中,f将调用double函数转换为0.6,然后与4相加变成4.6,由于构造函数取消隐公式转换,4无法转换为Fraction,但是加法需要4为Fraction,所以报错,而且4.6无法转换为Fraction,于是将报错。
代码如下
class Fraction
{
public:
explicit Fraction(int num, int den=1)
: m_numerator(num), m_denominator(den)
{ cout << m_numerator << ' ' << m_denominator << endl; }
operator double() const {
return (double)m_numerator / m_denominator;
}
Fraction operator+(const Fraction& f) {
cout << "operator+(): " << f.m_numerator << ' ' << f.m_denominator << endl;
return f;
}
private:
int m_numerator; //
int m_denominator; //
};
void test_conversion_functions()
{
cout << "test_conversion_functions()" << endl;
Fraction f(3,5);
double d = 4 + f; //4.6
cout << d << endl;
//! Fraction d2 = f + 4; //在构造函数Franction前加入explict关键字,隐式转换将取消,所以在执行d2 = f + 4过程中,f将调用double函数转换为0.6,然后与4相加变成4.6,由于构造函数取消隐公式转换,4无法转换为Fraction,但是加法需要4为Fraction,所以报错,而且4.6无法转换为Fraction,于是将报错。
}
16.智能指针
智能指针在语法上有三个很关键的地方,第一个是保存的外部指针,对应于上图的T* px,这个指针将代替传入指针进行相关传入指针的操作;第二个是重载“*”运算符,解引用,返回一个指针所指向的对象;第三个是重载“->”运算符,返回一个指针,对应于上图就是px。
- 迭代器也是智能指针
创建一个list迭代器对象,list::iterator ite;这里的list用于保存Foo对象,也就是list模板定义里的class T,operator*()返回的是一个(*node).data对象,node是link_type类型,然而link_type又是list_node*类型,这里的T是Foo,所以node是list_node类型,所以(node).data得到的是Foo类型的一个对象,而&(operator())最终得到的是该Foo对象的一个地址,即返回Foo 类型的一个指针。
17.仿函数
每个仿函数都是某个类重载“()”运算符,然后变成了“仿函数”,实质还是一个类,但看起来具有函数的属性。
- 每个仿函数其实在背后都继承了一个奇怪的类
18.namespace
可以自己定义一个命名空间
19.模板
- 与类模板不同的是,函数模板在使用是不需要显式地声明传入参数类型,编译器将自动推导类型。
- 成员模板在泛型编程里用得较多,为了有更好的可扩展性,以上图为例,T1往往是U1的基类,T2往往是U2的基类,可以看下面这个例子: 通过这种方法,只要传入的U1和U2的父类或者祖类是T1和T2,那么通过这样的方式可以实现继承和多态的巧妙利用,但反之就不行了
20.模板特化
1.必须要先有一个基础的模板
2.使用特换模板函数时格式有要求:
关键字template后面接一对空的尖括号<>
函数名/类名<特化类型>(特化类型 参数1, 特化类型 参数2 , …) 在函数名/类名后跟<>其中写要特化的类型
3.函数形参表必须要和模板函数的基础参数类型完全相同,如果不同编译器可能会报一些奇怪的错误。
21.模板偏特化
- 部分特化(个数上偏):将模板参数类表中的一部分参数特化。
2.范围上偏,泛化中的T与特化中T*是不一样的,T就是不同的
22.模板参数
中第一个错,因为list容器参数有好几个,只在private中模板化一个参数,虽然平常情况其他参数会给默认值,但是在这没有,只有接受一个参数,才不会出错
23.可变数目模板参数
24.auto
不能极端使用auto
25.reference
- reference可以看做是某个被引用变量的别名。
- object和其reference的大小相同,地址相同(全都是假象)
-
reference通常不用声明变量,而是用于参数类型和返回类型描述
-
光光加&是不能重载
前面都一样,在括号后加const算重载,是签名一部分
26.继承和组合都存在时,构造与析构情况
27.虚指针与虚函数表
定义了三个类,A、B和C,B继承于A,C继承于B,A中有两个虚函数,B中有一个,C中也有一个。编译器将A的对象a在内存中分配如上图所示,只有两个成员变量m_data1和m_data2,与此同时,由于A类有虚函数,编译器将给a对象分配一个空间用于保存虚函数表,这张表维护着该类的虚函数地址(动态绑定),由于A类有两个虚函数,于是a的虚函数表中有两个空间(黄蓝空间)分别指向A::vfunc1()和A::vfunc2();同样的,b是B类的一个对象,由于B类重写了A类的vfunc1()函数,所以B的虚函数表(青色部分)将指向B::vfunc1(),同时B继承了A类的vfunc2(),所以B的虚函数表(蓝色部分)将指向父类A的A::vfunc2()函数;同样的,c是C类的一个对象,由于C类重写了父类的vfunc1()函数,所以C的虚函数表(黄色部分)将指向C::vfunc1(),同时C继承了超类A的vfunc2(),所以B的虚函数表(蓝色部分)将指向A::vfunc2()函数。同时上图也用C语言代码说明了编译器底层是如何调用这些函数的,这便是面向对象继承多态的本质。
28.this指针
this指针其实可以认为是指向当前对象内存地址的一个指针,如上图所示,由于基类和子类中有虚函数,this->Serialize()将动态绑定,等价于(*(this->vptr)[n])(this)。
29.const
-
常量对象只能调用常量成员函数,非常量对象可以调用常量成员函数也可以调用非常量成员函数。
-
常量成员函数的const和non—const版本同时存在,常量对象只能调用const版本,非常量对象只能调用非常量版本
30.重载new和delete以及new[] delete[]
-
通过重载这两个,可以自己控制内存池
-
重载后第一个参数必须是size_t
31.重载new() delete()
想无声无息多分配一些内存时候,可以重载new().