C++的重要性质 小结 参照深入浅出MFC第二章
因为要学习MFC,所以我就看了深入浅出MFC这本书,看完第二章—C++的重要性质,感觉侯老师总结的真不错,比我一头闷在primer中看效果要好很多,当然我现在看第二张看的明白也与我看primer有很大的关系,但相比较于看primer而言,我还是感觉侯老师这个第二章讲得真是很不错。算作是复习,也算做是总结,我还是宁愿拿出一个小时的时间来总结下第二章讲的内容,写在这里,一方面强化一下记忆,另一方面以备后用。
1. C++的四大特性:面向对象,封装,继承,多态。
逐个说下四个特性的意思吧。
看到面向对象,不免想起面向过程。那么究竟什么叫面向对象,什么叫面向过程呢。举个简单的例子,日常生活中的轿车,坏了我们要去修,如果对于所有类型的轿车,我们写了一个修车的函数,那么这样我们可以理解成这是面向过程的。但如果我们这样想,对于所有的车,我们都有一个关于修车的函数,这里车这个对象是主体了,那么这样我们就可以理解成这是面向对象的了。(纯属个人理解,可能说的不太清楚)
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。类的实例化叫对象,对象的属性有两大成员,一是属性(member variable),一是方法(member function)。
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。多态性的实现是通过定义虚函数来实现的。通过虚函数,我们可以通过相同的指令来调用不同的函数,至于具体调用哪个函数,需要动态绑定来实现,编译器无法在编译时期判断到底是调用哪个函数,必须在执行期才能够决定。
2. 关于封装的public protected private
如果类中的成员变量或者函数是public类型的,那么它在任何地方都可以被访问,成员函数内部,或者是通过对象在类外调用。如果是protected类型的,那么它只能在类内部的成员函数里可以被访问,还有其子类的类内部成员函数里可以被访问,其它地方不能被访问。如果是private类型的,那么就只有在类内部的成员函数里才可以被访问了。
3. 类的继承访问特性
基类的访问特性 |
类的继承特性 |
子类的访问特性 |
Public Protected Private |
Public |
Public Protected No access |
Public Protected Private |
Protected |
Protected Protected No access |
Public Protected Private |
Private |
Private Private No access |
4. 关于纯虚函数
纯虚函数的定义如下类中所示:
class CShape
{
public:
CShape();
virtual void display() = 0;
private:
int x,y;
};
注:拥有纯虚函数的类我们通常都叫它为抽象类,需要记住的是抽象类是不能实例化对象的。也就是说,我如果用上面定义的类CShape去实例化一个对象的话编译器是会报错的。如果在Cshape的子类中还不实现display()这个函数的话,那么它的子类仍然是抽象类,不能实例化对象,知道它的子类把display()函数实现为止。
5. 类中静态成员变量和const成员常量的处理
我们首先定义一个类,其中含有静态成员变量和const成员常量如下:
class test
{
public:
test():c(5)
{
x = 1;
y = 1;
}
void output();
private:
int x;
int y;
static int t;
const int c;
};
Static成员变量不属于对象的一部分,而是属于类的一部分,所以程序可以在还没有诞生任何对象的时候就处理此种成员变量,但首先你必须初始化它。不要把static成员变量的初始化操作安排在类的构造函数中,因为构造函数可能一再被调用,而static变量的初始值应该只设定一次。也不要把static成员变量的初始化安排在头文件中,因为头文件可能会被载入许多地方。因此也就可能多次执行。我们应该在应用程序文件中,类以外的任何位置设定其初值,例如在main函数中,或全局函数中,或者是任何函数之外,比如我们可以这样初始化上类中的static成员变量t:
int test::t = 66;
int main()
{
…..
}
注意初始化的格式,首先是static成员变量的类型,然后是类作用域限定符,接着是变量名。不要忘记了成员变量的类型了。
再接着是const成员常量的初始化,它必须在构造函数的初始化列表中初始化,如下:
test():c(5)
{
x = 1;
y = 1;
}
如下的初始化方式是错误的:
test():
{
x = 1;
y = 1;
c = 5;
}
6. 一般来说,我们习惯把类的定义写在一个头文件中,而类中成员函数的实现写在一个cpp文件中,只需要在cpp文件中把定义类的头文件包含进来就可以了,但这个时候还是有了一个问题,那就是定义同一个类的头文件可能会被多次包含,这样就会造成编译器报这样的错误:class type redefinition,既是类的重定义。因为你只要包含一次头文件就要定义一次类。所以我们要避免同一个头文件中的内容被多次重复包含。
这个问题我们可以如下解决,只需要在每个头文件中用预定义宏来验证下就可以了。如下例所示:
#ifndef TEST_H
#define TEST_H
class test
{
public:
test():
{
x = 1;
y = 1;
c = 5;
}
void output();
private:
int x;
int y;
static int t;
const int c;
};
#endif
一般来说,为了避免重复定义宏,宏的名称都是这样组成的:类名称的大写_H,这样就可以避免重复包含同一个头文件了。
写这段话的目的是告诉自己:在定义类的头文件里加入预定义宏的判断是很有必要的,并不是随便的。
7. Template的使用。
这个看看例子就很容易明白了,我就不再多写了,只在这里写两个例子,自己一看就应该知道怎么用了。C++的Template有两种,一种针对function,另外一种针对class。
针对function的Template应用:
#include <iostream>
using namespace std;
template <class T>
T power(T base,int exponent)
{
T result = base;
if(exponent < 0)
return (T)0;
else if(exponent == 0)
return (T)1;
while(--exponent)
result *= base;
return result;
}
int main()
{
int i = power(5,2);
double d = power(2.5,2);
cout<<i<<endl;
cout<<d<<endl;
return 0;
}
针对class的应用:
#include <iostream>
using namespace std;
template <class T>
class CThree
{
public:
CThree(T t1,T t2,T t3);
T min();
T max();
private:
T a,b,c;
};
//这里需要注意下了,每个成员函数前面都要加上template <class T>,而且类名称应该使用CThree<T>
template <class T>
CThree<T>::CThree(T t1, T t2, T t3)
{
a = t1;
b = t2;
c = t3;
}
template <class T>
T CThree<T>::min()
{
T minab = a < b ? a : b;
return minab < c ? minab : c;
}
template <class T>
T CThree<T>::max()
{
T maxab = a > b ? a : b;
return maxab > c ? maxab : c;
}
//在main函数中定义类时要用CThree<T>,此时应该把类型T实例化才对,比如int或者double等。
int main()
{
CThree<int> obj1(1,2,3);
cout<<obj1.min()<<endl;
cout<<obj1.max()<<endl;
cout<<"----------"<<endl;
CThree<double> obj2(2.5,1.5,6.0);
cout<<obj2.min()<<endl;
cout<<obj2.max()<<endl;
return 0;
}
只需要记住这些个格式,用起来应该是没有什么障碍的。
8. 最后还是再补充几点吧。
1. 子类和基类构造函数和析构函数调用的顺序关系:构造函数是先调用基类的构造函数,再调用子类的构造函数,而析构函数则正好相反,是先调用子类的析构函数再调用基类的析构函数。
2. 如果基类的构造函数中含有参数,而子类的构造函数没有参数,如何通过子类的构造函数给基类的构造函数传递参数呢?可以参见以下这个例子:
#include <iostream.h>
class Animal
{
public:
//由于这里我们自己提供了Animal的构造函数,编译器不会再给我们提供默认的无参构造函数了
Animal(int h,int w)
{
cout<<"Animal construct"<<endl;
height = h;
weight = w;
}
~Animal()
{
cout<<"Animal deconstruc"<<endl;
}
private:
int height;
int weight;
};
class Fish:public Animal
{
public:
Fish()
{
cout<<"Fish construct"<<endl;
}
~Fish()
{
cout<<"Fish deconstruct"<<endl;
}
};
int main()
{
Fish myfish;
return 0;
}
在上面的例子中,Fish这个类是继承自Animal这个类,Fish的构造函数没有参数,而Animal的构造函数有参数,当我们编译上述程序时,会出现如下错误:
error C2512: 'Animal' : no appropriate default constructor available
分析下程序,当我们在主函数中要构造一个Fish类的对象时,要先构造基类Animal的对象,构造基类对象时,编译器会调用缺省的构造函数,缺省的构造函数是没有参数的构造函数,但因为我们已经有了一个有参数的构造函数,编译器就不会自动生成默认的构造函数了,所以会出现上述的错误。为了避免上述错误,我们不可能再加上一个没有参数的构造函数,解决的办法是,在Fish类的构造函数名后面加上传递的参数,形式如下:
Fish():Animal(400,300)
通过这种方式,可以给带参数的基类构造函数传递参数,从而成功构造子类的对象。
正确的程序如下:
#include <iostream.h>
class Animal
{
public:
//由于这里我们自己提供了Animal的构造函数,编译器不会再给我们提供默认的无参构造函数了
Animal(int h,int w)
{
cout<<"Animal construct"<<endl;
height = h;
weight = w;
}
~Animal()
{
cout<<"Animal deconstruc"<<endl;
}
private:
int height;
int weight;
};
class Fish:public Animal
{
public:
Fish():Animal(400,200)
{
cout<<"Fish construct"<<endl;
}
~Fish()
{
cout<<"Fish deconstruct"<<endl;
}
};
int main()
{
Fish myfish;
return 0;
}
好了就写到这里了,果然如老板所说,在实验室效率确实不太高,我这还算是集中精力了,还是比预想的时间多用了半个多小时,bless.
刚才吃饭时,老板反复强调,老板告诉我们在提高实验室总体时间的同时也要注重提高实验室效率,为了晚上在实验室的效率,我决定先回去睡一觉了,sigh~