内联函数:函数调用是有时间开销的。如果函数本身只有几条语句,执行非常快,而且函数被反复执行很多次,相比之下调用函数所产生的这个开销就会显得比较大。
为了减少函数调用的开销,引入了内联函数机制。编译器处理对内联函数的调用语句时,是将整个函数的代码插入到调用语句处,而不会产生调用函数的语句。
重载函数:一个或多个函数,名字相同,然而参数个数或参数类型不相同,这叫做函数的重载。编译器根据调用语句的中的实参的个数和类型判断应该调用哪个函数。
(1) int Max(double f1,double f2) { }
(2) int Max(int n1,int n2) { }
(3) int Max(int n1,int n2,int n3) { }
Max(3.4,2.5); //调用 (1)
Max(2,4); //调用 (2)
Max(1,2,3); //调用 (3)
Max(3,2.4); //error,二义性,既可以类型转换后调用(1),也可以类型转换后调用(2)
C++中,定义函数的时候可以让最右边的连续若干个参数有缺省值,那么调用函数的时候,若相应位置不写参数,参数就是缺省值。
函数参数可缺省的目的在于提高程序的可扩充性。即如果某个写好的函数要添加新的参数,而原先那些调用该函数的语句,未必需要使用新增的参数,那么为了避免对原先那些函数调用语句的修改,就可以使用缺省参数。
面向对象的四个基本概念:抽象、封装、继承、多态
抽象:将一类客观事物的共同属性归纳出来,形成一个数据结构。将这类事物所能进行的一些行为和操作归纳起来形成函数,这些函数可以来操作具体的数据结构。
继承:将数据结构和算法对应地捆绑在一起,形成类。
class B{
inline void func1();
void func2()
{
};
};
void B::func1() { }//注意void 和 B 的顺序,即返回值类型和类名的顺序
可以有多个构造函数
构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数。
有时对象没被初始化就使用,会导致程序出错。
构造函数最好是public的,private构造函数不能直接用来初始化对象
class Test {
public:
Test( int n) { } //(1)
Test( int n, int m) { } //(2)
Test() { } //(3)
};
Test array1[3] = { 1, Test(1,2) };
// 三个元素分别用(1),(2),(3)初始化
Test array2[3] = { Test(2,3), Test(1,2) , 1};
// 三个元素分别用(2),(2),(1)初始化
Test * pArray[3] = { new Test(4), new Test(1,2) };
//两个元素分别用(1),(2) 初始化
对最后一个,pArray是一个3元素的指针数组,元素是指针,并不会导致对象的生成。所以只是在调用new的时候调用了构造函数。
没有形参、或所有形参都有默认实参的构造函数为默认构造函数。
默认构造函数说明了当定义对象时没有为它提供(显示的)初始化式时应该怎么办。
只要定义一个对象时,没有提供初始化式,就使用默认构造函数。
即是编译器自动生成的默认构造函数。(注意区分合成的默认构造函数和默认构造函数!!!)
一个类只要定义了一个构造函数,编译器就不再生成默认构造函数。(依据是:如果类在某种情况下需要控制对象初始化,则该类很可能在所有情况下都需要控制。)
合成的默认构造函数使用与变量初始化相同的规则来初始化:
具有类类型的成员通过运行各自的默认构造函数来进行初始化;
内置和复合类型的成员,如指针和数组,只对定义在全局作用域中的对象才初始化;如果对象是定义在局部作用域中,则内置和复合类型的成员不进行初始化。
只有一个参数,即对同类对象的引用。(个人理解,它也是一种构造函数,用来初始化。)
形如 X::X( X& )或X::X(const X &), 二者选一。后者能以常量对象作为参数
如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
不允许有形如 X::X( X )的构造函数。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句 c2是由复制构造函数来初始化的。
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <可以看到这里a1会调用复制构造函数,不一定等于实参a2;而如果调用的是默认复制构造函数,则a1和a2相等。说明,通过自己编写的复制构造函数可使得形参和实参不等。
return 0;
}
程序输出结果为: Copy constructor called
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <这里调用.v的对象肯定要先生成,生成的时候肯定要调用构造函数来初始化,调用的是哪个构造函数呢?调用的是复制构造函数,来初始化。这是个临时对象。 那复制构造函数的形参是什么呢?是Func函数的返回对象。
//可以看到,临时对象不一定和Func返回的对象相等。因为取决于你编写的复制构造函数。这个和2中的形参和实参不一定相等是一样的。
return 0;
}
输出结果:
Copy constructor called 4
注:按规范来说,复制构造函数只能做“复制”的工作,而不能做其他如输入输出、修改外部变量(包括静态变量)等事情。
某些编译器(包括你做测试的这两个)假设程序员严格按照上述规范,将一些不必要的复制构造函数过程略去(比如VS2013和Codeblocks2013,因为函数返回的对象给变量后就没有用了,所以就直接给变量了,而不会严格按照标准“复制”一个新的对象给变量然后删掉函数返回对象)。这样可以极大地提高程序运行效率,尤其是当复制构造函数很复杂的时候。然而,若程序员不按照规范写,就会因省略复制构造导致出现一些非预想的情况。
这也提醒大家,本课程例题、作业里出现的在复制构造函数中输出、修改外部变量等只是为了让大家充分理解复制构造函数的特性。在实际应用时,请勿使用类似操作,以防出现不必要的问题。
class Complex {
public:
double real, imag;
Complex( int i ) { //类型转换构造函数
cout << “IntConstructor called” << endl;
real = i; imag = 0;
}
Complex( double r, double i )
{ real = r; imag = i; }
};
int main () {
Complex c1(7, 8);
Complex c2 = 12;//这里没有生成一个临时对象
c1 = 9; // 9被自动转换成一个临时Complex对象
cout << c1.real << "," << c1.imag << endl;
return 0;
}
因为类型转换构造函数也是一种构造函数。
成员函数的一种:名字与类型相同,在前面加 ‘~’,没有参数和返回值
一个类最多只有一个析构函数
对象消亡时—>自动被调用。在对象消亡前做善后工作,释放分配的空间等
定义类时没写析构函数, 则编译器生成缺省析构函数。不涉及释放用户申请的内存释放等清理工作
定义了析构函数, 则编译器不生成缺省析构函数
对象数组生命期结束时—>对象数组的每个元素的析构函数都会被调用
delete 运算导致析构函数调用
Ctest * pTest;
pTest = new Ctest; //构造函数调用
delete pTest; //析构函数调用
------------------------------------------------------------------
pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest; //析构函数调用3次
class Demo {
int id;
public:
Demo( int i )
{
id = i;
cout << “id=” << id << “ Constructed” << endl;
}
~Demo()
{
cout << “id=” << id << “ Destructed” << endl;
}
};
Demo d1(1);
void Func(){
static Demo d2(2);
Demo d3(3);
cout << “Func” << endl;
}
int main (){
Demo d4(4);
d4 = 6;
cout << “main” << endl;
{ Demo d5(5);} //作用域
Func();
cout << “main ends” << endl;
return 0;
}
输出:
id=1 Constructed
id=4 Constructed
id=6 Constructed
id=6 Destructed
main
id=5 Constructed
id=5 Destructed
id=2 Constructed
id=3 Constructed
Func
id=3 Destructed
main ends
id=6 Destructed
id=2 Destructed
id=1 Destructed
每个对象都是在其作用域结束时被释放的。
如果两个或多个对象在同一处结束作用域,则“先声明的后释放”。
局部变量比静态变量先释放的原因是局部变量的作用域先结束。
静态成员:在说明前面加了static关键字的成员。
普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用与某个对象。
因此静态成员不需要通过对象就能访问。
sizeof 运算符不会计算静态成员变量。
class CMyclass { int n;static int s; }; 则 sizeof( CMyclass ) 等于 4
静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
静态成员函数本质上是全局函数。
设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
考虑一个需要随时知道矩形总数和总面积的图形处理程序。可以用全局变量来记录总数和总面积。用静态成员将这两个变量封装进类中,就更容易理解和维护。
class CRectangle {
private:
int w, h;
static int nTotalArea;
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal();
};
CRectangle::CRectangle(int w_,int h_) {
w = w_;
h = h_;
nTotalNumber ++;
nTotalArea += w * h; }
CRectangle::~CRectangle() {
nTotalNumber --
;
nTotalArea
-= w * h;
}
void CRectangle::PrintTotal() {
cout << nTotalNumber << "," << nTotalArea << endl; }
int CRectangle::nTotalNumber = 0;
int CRectangle::nTotalArea = 0;
// 必须在定义类的文件中对静态成员变量进行一次说明
//或初始化。否则编译能通过,链接不能通过。
int main()
{
CRectangle r1(3,3), r2(2,2);
//cout << CRectangle::nTotalNumber; // Wrong , 私有
CRectangle::PrintTotal();
r1.PrintTotal();
return 0;
}
输出结果:
2,13
2,13
要注意一点,就是在C++里面啊,静态成员变量你必须拿到外面,就是所有的函数外面来单独的给它声明一下。在声明的同时,你可以对它进行初始化,也可以不初始化。
void CRectangle::PrintTotal()
{
cout << w << "," << nTotalNumber << "," << nTotalArea << endl; //wrong
}
CRetangle::PrintTotal(); //解释不通,w 到底是属于那个对象的?
附:此CRectangle类写法有缺陷。即在调用默认的复制构造函数时没有对总个数增加,而调用析构函数时总个数有减少。解决方法就是,自己定义一个复制构造函数。
成员对象: 一个类的成员变量是另一个类的对象
包含 成员对象 的类叫 封闭类 (Enclosing)
class CTyre { //轮胎类
private:
int radius; //半径
int width; //宽度
public:
CTyre(int r, int w):radius(r), width(w) { }
};
class CEngine { //引擎类
};
class CCar { //汽车类,即是“封闭类”
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
};
CCar::CCar(int p, int tr, int w):price(p), tyre(tr, w){};
int main(){
CCar car(20000,17,225);
return 0;
}
如果 CCar 类不定义构造函数, 则CCar car; // error 编译出错
生成封闭类对象的语句—>明确 “对象中的成员对象”
当封闭类对象生成时,
成员对象的构造函数调用顺序
当封闭类的对象消亡时,
析构函数顺序和构造函数的调用顺序相反
class CTyre {
public:
CTyre() { cout << "CTyre contructor" << endl; }
~CTyre() { cout << "CTyre destructor" << endl; }
};
class CEngine {
public:
CEngine() { cout << "CEngine contructor" << endl; }
~CEngine() { cout << "CEngine destructor" << endl; }
};
class CCar
{
private:
CEngine engine;
CTyre tyre;
public:
CCar( ) { cout << “CCar contructor” << endl; }
~CCar() { cout << "CCar destructor" << endl; }
};
int main()
{
CCar car;
return 0;
}
程序的输出结果是:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructor
class CCar; //提前声明 CCar类, 以便后面CDriver类使用
class CDriver {
public:
void ModifyCar( CCar * pCar) ; //改装汽车
};
class CCar {
private:
int price;
friend int MostExpensiveCar( CCar cars[], int total); //声明友元
friend void CDriver::ModifyCar(CCar * pCar); //声明友元
};
void CDriver::ModifyCar( CCar * pCar)
{
pCar->price += 1000; //汽车改装后价值增加
}
int MostExpensiveCar( CCar cars[], int total) //求最贵汽车的价格
{
int tmpMax = -1;
for( int i = 0; i < total; ++i )
if( cars[i].price > tmpMax)
tmpMax = cars[i].price;
return tmpMax;
}
int main()
{
return 0;
}
将一个类的成员函数(包括
构造, 析构函数) 设置为另一个类的友元
class B {
public:
void function();
};
class A {
friend void B::function();
};
class CCar {
private:
int price;
friend class CDriver; //声明CDriver为友元类
};
class CDriver {
public:
CCar myCar;
void ModifyCar() { //改装汽车
myCar.price += 1000; // CDriver是CCar的友元类可以访问其私有成员
}
};
int main() { return 0; }
Note:友元类之间的关系不能传递, 不能继承
class CCar
{
public:
int price;
void SetPrice(int p);
};
void CCar::SetPrice(int p)
{
price = p;
}
int main() {
CCar car;
car.SetPrice(20000);
return 0;
}
翻译到 C 语言:
struct CCar {
int price;
};
void SetPrice(struct CCar * this,int p)
{
this->price = p;
}
int main() {
struct CCar car;
SetPrice( & car,20000);
return 0;
}
成员函数的翻译:成员函数会被翻译成一个全局的函数。然後这个全局函数参数个数要比成员函数多一个。多出来的这个参数,就是所谓的一个this指针。那这个this指针指向谁呢?就指向在C++程序里面那个成员函数所作用的这个对像。
class A
{
int i;
public:
void Hello() { cout << "hello" << endl; }
}; —> void Hello(A * this ) { cout << "hello" << endl; }
int main()
{
A * p = NULL;
p->Hello();//结果会怎样? —> Hello(p);
} // 输出:hello
class A
{
int i;
public:
void Hello() { cout <i << "hello"<< endl; }
//this若为NULL,则出错!!
int main()
{
A * p = NULL;
p->Hello();
Hello(p);
}
class Demo{
private :
int value;
public:
void SetValue() { }
};
const Demo Obj; // 常量对象
常量对象不能修改它的成员变量值
常量对象不能调用非常量成员函数(静态成员函数除外) 见下面例子的两个错误。
class Sample
{
public:
int value;
void GetValue() const;
void func() { };
Sample() { }
};
void Sample::GetValue() const
{
value = 0; // wrong
func(); //wrong
}
int main() {
const Sample o;
o.value = 100; //err.常量对象不可被修改
o.func(); //err.常量对象上面不能执行非常量成员函数 编译器不会去分析func函数到底做了什么,它只看到o是常量对象,而func是非常量成员函数,func是有可能改变对象的成员变量的。
o.GetValue(); //ok,常量对象上可以执行常量成员函数
return 0;
} //在Dev C++中,要为Sample类编写无参构造函数才可以,Visual Studio2010中不需要
class Sample {
…
};
void PrintfObj(Sample & o)
{
……
}
对象引用作为函数的参数有一定风险性,若函数中不小心修改了形参o,则实参也跟着变,这可能不是我们想要的。如何避免?
class Sample {
…
};
void PrintfObj( const Sample & o)
{
……
}//这样函数中就能确保不会出现无意中更改o值的语句了。