------------------------------------------------------------
引用的实质类似于c语言中的指针常量
int a=10;
int &b=a;//此句类似于int *const b=&a;
以此类推到常引用
const int &b=a;//类似于const int *const b=&a;
引用在使用的时候,相当于在其名称前加了一个*(即每次用到b的时候,都等效于*b)。
因此可以自然的想到,引用的存储类似于指针的存储
因此下例中,结果为4#include <iostream>
using namespace std;
struct AA
{
int &b;
};
int main()
{
cout<<sizeof(AA)<<endl;
return 0;
}
------------------------------------------------------------
1.内联函数在编译时直接将函数体插入到函数调用的地方
2.inline只是一种请求,编译器不一定允许这种请求
3.内联函数省去了普通函数调用时压栈、跳转和返回的开销
------------------------------------------------------------
有参构造函数的调用方法有三种
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
a=0;
b=0;
}
Test(int aa,int bb)
{
a=aa;
b=bb;
}
Test(int aa)
{
a=aa;
b=0;
}
void print()
{
cout<<"a:"<<a<<endl<<"b:"<<b<<endl;
}
private:
int a;
int b;
};
int main()
{
//有参构造函数的调用方法有三种
//1.括号法
Test t1(1,2);
t1.print();//a:1 b:2
//2.等号法
Test t2=(3,4);//c++对等号符进行了功能增强,此处应用逗号表达式理解
t2.print();//a:4 b:0
Test t3=5;
t3.print();//a:5 b:0
//前两种方法是c++编译器自动的调用构造函数
//3.直接调用构造函数 手动的调用构造函数
Test t4=Test(6,7);//此方法将产生一个匿名对象;此处是t4的初始化,比较:t4=t1(赋值)
t4.print();//a:6 b:7
return 0;
}
------------------------------------------------------------
拷贝构造函数的4种调用机制
#include <iostream>
using namespace std;
class Test
{
public:
Test()
{
a=0;
b=0;
}
Test(int aa,int bb)
{
a=aa;
b=bb;
}
Test(const Test& t)
{
a=t.a;
b=t.b;
}
void print()
{
cout<<"a:"<<a<<endl<<"b:"<<b<<endl;
}
int getA()
{
return a;
}
~Test()
{
cout<<"析构函数被调用了"<<endl;
}
private:
int a;
int b;
};
void f(Test &t)
{
cout<<"a:"<<t.getA()<<endl;
}
Test g()
{
Test tt(11,22);
return tt;
}
int main()
{
//拷贝构造函数的4种调用机制
Test t1(1,2);
//1.基本调用1
Test t2=t1;//注意区别:Test t2; t2=t1;(赋值操作(赋值构造函数))
t2.print();
//2.基本调用2
Test t3(t1);
t3.print();
//3.作为函数参数
f(t1);
//4.作为函数返回值 注意:尝试调试体会类的创建与析构流程
Test t4=g();//用匿名对象初始化t4,此时匿名对象直接转换成t4
t4.print();
return 0;
}
关于第四种,即C++中对象作为函数返回值产生的匿名类详解:
------------------------------------------------------------
C++中对类和对象的存储管理
#include <iostream>
using namespace std;
class Test
{
public:
int getA()
{
return a;
}
void setA(int aa)
{
a=aa;
}
//成员函数放在代码区中
private:
int a;//4
int b;//4
static int c;//静态成员变量(函数)放在全局数据区
};
int main()
{
cout<<sizeof(Test)<<endl;//8
return 0;
}
1.C++类对象中的成员变量和成员函数是分开存储的
成员变量:
普通成员变量:存储于对象中,与struct变量有相同的内存布局和字节对齐方式
静态成员变量:存储于全局数据区
成员函数:存储于代码段中
2.C++中类的普通成员函数都隐式的包含一个指向当前对象的this指针
------------------------------------------------------------
C++中常常看到类的成员函数后带有const,如
void print() const
{
......
}
其中const修饰的是this指针,即当前使用的对象的内存空间,因此只能在该函数中,只能进行读的操作,不能对该对象的成员变量进行修改等操作。
假设上例中的函数是在Test类里的,则上述函数等价于void print(const Test *const this),因此,可以推测,对this指针的修改也是不允许的。
若没有加const修饰该函数的话,则相当于void print(Test *const this)
------------------------------------------------------------
C++中的运算符重载
运算符重载有两种方法:全局变量法(友元函数法)与成员函数法
注意:
1.类的成员变量一般为私有属性,因此全局变量使用时,需声明为友元函数
2.前置++与后置++是编译器自动匹配的,如调用c1++这句话时,编译器检测到++是后置的,然后就去找带参数int的重载函数,使用这个重载函数。但如果在程序中只留一个前置++的函数时(删除掉后置++的函数),调用c1++这句话还是可以调用,并且有输出结果,但是有个警告信息
warning C4620: no postfix form of 'operator ++' found for type 'Complex', using prefix form
C:\Users\Administrator\Desktop\Cpp2.cpp(4) : see declaration of 'Complex'
说明编译器检测到了应该用后置++函数,但是没有后置++函数,所以只能用前置++这个函数
3.现在使用过程中的运算符重载大多使用成员函数法,这更容易理解。而全局函数(友元函数)重载的正确打开方式(用途)--重载输入、输出流,例如要输出一个Complex类,就必须重载输出流,而这通过友元函数法很容易实现,如果要使用成员函数法的话则必须得到ostream类,而这是很困难的
4.友元函数重载运算符常用于运算符的左右操作数类型不同的情况。
如:Complex c1(1,2),c2(3,4);
c2=c1+27;
c2=27+c1;//27不是Complex对象,不能调用函数
5.C++中不能用友元函数重载的运算符:
= () []->
#include <iostream>
using namespace std;
class Complex
{
public:
Complex(int x,int y)
{
a=x;
b=y;
}
void print()
{
cout<<a<<"+"<<b<<"i"<<endl;
}
friend Complex operator+(Complex &c1,Complex &c2);
Complex operator-(Complex &c2);
friend Complex& operator++(Complex &c1);
Complex& operator--();
friend Complex operator++(Complex &c1,int);
Complex operator--(int);
friend ostream& operator<<(ostream &out,Complex &c1);
private:
int a;
int b;
};
//成员函数重载+
Complex Complex::operator-(Complex &c2)
{
Complex temp(this->a-c2.a,this->b-c2.b);
return temp;
}
//全局函数重载-
Complex operator+(Complex &c1,Complex &c2)
{
Complex temp(c1.a+c2.a,c1.b+c2.b);
return temp;
}
//全局函数重载前置++
Complex& operator++(Complex &c1)
{
c1.a++;
c1.b++;
return c1;
}
//成员函数重载前置--
Complex& Complex::operator--()
{
this->a--;
this->b--;
return *this;
}
//全局函数重载后置++
Complex operator++(Complex &c1,int)
{
Complex temp=c1;
c1.a++;
c1.b++;
return temp;
}
//成员函数重载后置--
Complex Complex::operator--(int)
{
Complex temp=*this;
this->a--;
this->b--;
return temp;
}
//全局函数重载的用途(重载输出流...)
ostream& operator<<(ostream &out,Complex &c1)//注意ostream是一个类,且并不是Complex的一个成员
{
out<<c1.a<<" + "<<c1.b<<"i";
return out;
}
int main()
{
Complex c1(1,2),c2(3,4);
//全局函数法(友元函数法)
Complex c3=c1+c2;
c3.print();//4+6i
//成员函数法
Complex c4=c3-c1;
c4.print();//3+4i
Complex c5=c2.operator -(c4);
c5.print();//0+0i
//全局函数重载前置++
++c1;
c1.print();//2+3i
//成员函数重载前置--
--c2;
c2.print();//2+3i
//全局函数重载后置++
c1++;
c1.print();//3+4i
//成员函数重载后置--
c2--;
c2.print();//1+2i
//重载<<
cout<<c1<<endl<<c2<<endl;
return 0;
}
------------------------------------------------------------
继承和组合混搭情况下,构造和析构调用原则
原则:
先构造父类(递归),再构造成员变量,最后构造自己
先析构自己,再析构成员变量,最后析构父类(递归)
------------------------------------------------------------
类型兼容原则:
子类对象可以当做父类对象使用
子类对象可以直接赋值给父类对象
子类对象可以直接初始化父类对象
父类指针可以直接指向子类对象
父类引用可以直接引用子类对象
------------------------------------------------------------
重载重写重定义
函数重载
必须在同一个类中进行
子类无法重载父类的函数,父类同名函数将被名称覆盖
重载是在编译期间根据参数类型和个数决定函数调用
函数重写
必须发生于父类与子类之间
并且父类与子类的函数必须有完全相同的类型
使用virtual声明之后能够产生多态(如果不使用virtual,那叫重定义)
多态是在运行期间根据对象的类型决定函数调用
#include <iostream>
using namespace std;
//重写发生在2个类之间
//重载必须在一个类之间
//重写分为两类
//1.虚函数重写 将发生多态
//2.非虚函数重写(重定义)
class Parent
{
public:
//这三个函数是重载
virtual void func()
{
cout<<"Parent func() do..."<<endl;
}
virtual void func(int i)
{
cout<<"Parent func(int i) do..."<<endl;
}
virtual void func(int i,int j)
{
cout<<"Parent func(int i,int j) do..."<<endl;
}
void abc()
{
cout<<"Parent abc() do..."<<endl;
}
};
class Child:public Parent
{
public:
void abc()
{
cout<<"Child abc() do..."<<endl;
}
virtual void func(int i,int j)
{
cout<<"Child func(int i,int j) do..."<<endl;
}
virtual void func(int i,int j,int k)
{
cout<<"Child func(int i,int j,int k) do..."<<endl;
}
};
int main()
{
Child c1;
//c1.func();//error
c1.abc();
c1.Parent::func();
return 0;
}
上例中Parent类中三个func()函数为重载,Child类中两个func()函数为重载,而Parent类中func(int i,int j)在子类中进行了重写(有virtual关键字修饰),Parent类中的abc()函数则在子类Child中进行了重定义(没有virtual关键字修饰)
main函数中的error处原因:
子类无法重载父类的函数,父类同名函数将被名称覆盖
C++编译器看到func名字在子类中已经存在了,所以不会去父类中去找而在子类中没有无参的func函数,所以报错
解决方法-----使用域作用符:c1.Parent::func();
------------------------------------------------------------
产生多态时vptr指针的分布初始化
#include <iostream>
using namespace std;
class Parent
{
public:
Parent(int a=0)
{
this->a=a;
print();//问题由来:构造函数中调用了虚函数
//此处是调用父类的构造函数还是子类的构造函数?
}
virtual void print()
{
cout<<"I'm Parent"<<endl;
}
private:
int a;
};
class Child:public Parent
{
public:
Child(int a=0,int b=0):Parent(a)
{
this->b=b;
print();
}
virtual void print()
{
cout<<"I'm Child"<<endl;
}
private:
int b;
};
void howToPrint(Parent *p)
{
p->print();
}
int main()
{
Child c1;
//vptr指针的分布初始化
//当执行父类的构造函数时,c1.vptr指向父类的虚函数表
//当父类的构造函数运行完毕后,会把c1.vptr指向子类的虚函数表
return 0;
}
上例中,当执行父类的构造函数时,c1.vptr指向父类的虚函数表,当父类的构造函数运行完毕后,会把c1.vptr指向子类的虚函数表,因此结果为
I'm Parent
I'm Child
说明:通过虚函数表指针vptr调用重写函数实在程序运行时进行的,因此需要通过寻址操作才能确定真正应该调用的函数,而普通成员函数时在编译时就确定了调用的函数,在效率上,虚函数的效率要低很多
出于效率考虑,没有必要将所有得到成员函数都声明为虚函数
上例中,C++编译器执行howToPrint函数,不需要区分是子类对象还是父类对象
------------------------------------------------------------
函数模板中,用友元函数重载<<>>操作符
friend ostream& operator<<<T>(ostream &out,Complex<T> &c3);
类模板中的类写成.h .cpp形式时,应该在测试使用中包含.cpp而不是.h
------------------------------------------------------------
C++中的类型转换
static_cast 静态类型转换,如int转换成char
reinterpret_cast 重新解释类型
dynamic_cast 命名上理解是动态类型转换,如子类和父类之间的多态类型转换
const_cast 字面上理解就是去const类型
4种类型转换格式:
TYPE B=static_cast<TYPE>(b)
------------------------------------------------------------
关于异常
1.如果接受异常的时候使用一个异常变量,则拷贝构造异常变量
2.如果接受异常的时候使用一个异常变量的引用,则使用throw时候的那个变量
3.catch中不能存在同类型的变量和引用