Feed on
Posts
Comments

Category Archive for 'C&C++'

C++继承和python继承不同点

class A
{
public:
void f1()
{
cout

Read Full Post »

C和C++的异常处理

异常(Exception)是程序可能检测到,运行时刻不正常的情况,如被0除、数组越界访问或者堆空间申请失败等等。
标准C并没有提供异常处理机制,但是提供了两个特殊的函数:setjmp()和longjmp(),这两个函数是结构化异常的基础,C里可以利用这两个函数的特性来实现异常。在头文件setjmp.h里有两个函数一个类型(jmp_buf):
int setjmp ( jmp_buf env );
void longjmp (jmp_buf env, int val);
C.1 jmp_buf是一个结构体,用于保存当前程序现场(保存当前需要用到的寄存器的值),结构如下:
typedef struct
{
unsigned j_sp; // [...]

Read Full Post »

RTTI

RTTI全称是RunTime Type Identification,即运行时类型识别,这是C++的一个新特性。程序能够使用基类类型指针或引用来检索这些指针或引用在运行时所指对象的实际的类型:派生类或者基类。通过两个操作符来提供RTTI:(1)typeid操作符,返回指针或者引用的实际类型;(2)dynamic_cast操作符,将基类类型指针或者引用安全的转换位派生类类型的指针或者引用。
1. typeid操作符该操作符的使用形如:typeid(e),其中e是任意表达式或者类型名。
如果表达式的类型是类类型且这个类中包含一个或者多个虚函数,则表达式的动态类型可能不同于它的静态类型;如果表达式的类型是内置类型或者是类类型但是没有包含任何虚函数,则表达式的动态类型和静态类型一致。例如,如果表达式是基类类型引用或者是对基类指针解引用,则表达式的静态编译时类型是基类型,但如果指针或引用实际指向派生类对象,则typeid就会告诉我们表达式的类型是派生类类型。
typeid操作符的结果是名为type_info的标准库类型的对象引用。要使用这个类,必须include头文件typeinfo。type_info这个类的默认构造函数、复制构造函数以及赋值操作符都定义成了private,所以不能定义或者复制type_info类型对象,只能通过typeid操作符来创建type_info类对象。这个类里有一个函数是经常用的name(),它返回C风格字符串,是类型名字的可显示版本。
常见用途如下:derived *pd=new derived;base *pb=pd;//比较两个表达式的类型if (typeid(*pb) == typeid(*pd)) {   //do something}//比较表达式类型和特定类型if (typeid(*pb) == typeid(derived)){   //do something}注意:只有当typeid的操作数是带虚函数的类类型的对象的时候,才返回动态类型信息。测试指针只返回指针的静态类型。所以,如下测试用用于是失败的:if (typeid(pb) == typeid(derived)){   //do something}
2. dynamic_cast操作符它实际上是C++中4中强制类型转换操作符之一,但是dynamic_cast涉及运行时类型检查,所以它能安全的将基类类型对象的引用或指针转换为同一继承层次中其他类型的引用或者指针。和typeid操作符有点不同,dynamic_cast待转换的基类类型中必须包含一个或者多个虚函数,否则不能使用dynamic_cast操作符(编译都通过不了)。
转换指针方式如下:derived * pd = dynamic_cast<derived*>(pb);在运行时检查,如果pb指向继承类对象,那么转换成功;如果pb 指向基类对象,那么转换的结果是0,即pd为0。
转换引用方式如下:derived & rd=dynamic_cast<derived&>(rb);当rb实际引用的是一个基类对象,则转换失败,抛出一个std::bad_cast异常;当rb实际引用的是一个继承类对象,则转换成功。
在实际的应用中,当我们不能修改基类,但能在继承类中增加函数,此时我们无法使用虚函数来调用继承类的新增函数,但是可以使用dynamic_cast来将基类指针或者引用安全的转换为继承类指针或者引用,这样就可以访问新增的函数。

Read Full Post »

重载、覆盖、隐藏

这里讲重载(overload)、覆盖(override)、隐藏是基于OOP的继承、多态来阐述。 三者有一点是完全相同的,那就是讨论的函数都是同名的。
1、重载比较好理解,是指与许存在多个同名函数,而这些函数的参数表不同(或者参数个数不同、或者参数类型不同、或者两者皆不同)。注意:仅仅是函数的返回值不同的重载,编译器是会嗷嗷叫的!
类成员函数被重载的特征:

相同的范围(在同一个类中);
函数名字相同;
参数不同(或者参数个数不同、或者参数类型不同、或者两者皆不同);
virtual 关键字可有可无。

这里要偏离下主题,备案一个刚刚获知的知识点。一直以来,我以为复制构造函数有以下两种重载原型:
Myclass(Myclass &);
Myclass(const Myclass &);
今天又学一手,还有第三种复制构造函数原型:
Myclass(volatile Myclass &);
2、言归正传,而覆盖是指派生类函数覆盖基类函数,它总是和继承、多态扯上千丝万缕的关系,可谓你中有我,我中有你!它的特征如下:

不同的范围(分别位于派生类与基类);
函数名字相同;
参数相同(vs2005里两个函数的返回值也要一样,否则报error);
基类函数必须有virtual 关键字。

也就是说分别处于互为继承关系的类中,两个完全相同的一模一样的虚函数才有覆盖的概念!
看如下代码:
#include <iostream>
using namespace std;
class base{public:    void f(int x) { cout<< “base::f(int)” << endl; }    void f(float y) { cout << “base::f(float)” << endl;}    virtual void g(void) { cout << “base::g(void)” << endl; }};
class derived:public base{public:
    //这里的virtual可要可不要,因为基类函数有virtual,继承类同名同参数函数自动加上virtual    virtual void g(void) { cout << “derived::g(void)” << endl;}};
int [...]

Read Full Post »

#pragma预处理指令

在所有编译预处理指令中,#pragma指令是最复杂的,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。它的格式一般为:#pragma para  其中para为参数。下面罗列一些我见到过的参数。
1.message 参数
使用方法:#pragma message(“文本消息”)
作用:当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。注意:这里的编译输出窗口不是指打印程序输出结果的那个黑黑控制台窗口,而是指IDE下面输出编译信息,例如:给你报编译错误的那个窗口。
2. warning 参数
例如:#pragma warning(diable:4507;once:4385;error:164)
           #pragma  warning(disable:4507  34)    //  不显示4507和34号警告信息             #pragma  warning(once:4385)                //  4385号警告信息仅报告一次             #pragma  warning(error:164)                //  把164号警告信息作为一个错误。 
同时这个pragma  warning  也支持如下格式:             #pragma  warning(  push  [  ,n  ]  )             #pragma  warning(  pop  )  这里n代表一个警告等级(1~4)。             #pragma  warning(  push  )保存所有警告信息的现有的警告状态。             #pragma  warning(  push,  n)保存所有警告信息的现有的警告状态,并且把全局警告  等级设定为n。               #pragma  warning(  [...]

Read Full Post »

这里谈论的关于类的关键字或保留字,针对结构体同样适合,在C++中,类和结构体其实没有什么区别了,同样支持继承、构造、析构等等,唯一差别是,两者的成员在默认的情况下,结构体中public访问权限,而类中private访问权限。
下面我们来理解下类和对象:
类是将数据成员和进行于其上的一系列操作(成员函数)封装在一起。 对象是类的实例化,怎样理解实例化?其实每一个实例对象都只是对其中的数据成员初始化,内存映像中每个对象仅仅保留属于自己的那份数据成员副本。而成员函数对于整个类而言却是被所有的实例化的类对象共享的,即一个类只保留一份成员函数。 那么每个对象怎样和这些可以认为是“分离”的成员函数发生联系,即成员函数如何操作对象的数据成员?记住this指针,无论对象通过(.)操作或者(->)操作调用成员函数,编译时刻,编译器都会将这种调用转换成我们常见的全局函数的形式,并且多出一个参数(一般这个参数放在第一个),然后将this指针传入这个参数。于是就完成了对象与成员函数的绑定(或联系)。
1. 类的定义使用到三种访问修饰符private/public/protected,它们要控制的是一个函数(施事)对一个类的成员(包括成员变量及成员方法)的访问权限。当我们说一个类可以访问XXX,其实暗指这个类的成员函数可以访问XXX。
private: 只能由该类中的函数、其友元函数访问,除此之外的用户程序都不能通过类对象对其进行访问; protected: 可以被该类中的函数、子类的函数(public继承下)、以及其友元函数访问,除此之外的用户程序都不能通过类对象对其进行访问; public: 可以被该类中的函数、子类的函数(public继承下)、其友元函数访问,在用户程序中也可以由该类的对象对其进行访问。
总结起来就是:

一个类友元(包含友元函数或者友元类的所有成员函数)可以访问该类的任何成员(包括成员变量及成员方法)。
除去友元外,private成员只有该类自身的成员函数可以访问,protected成员只有该类的成员函数及其派生类的成员函数可以访问,public成员则所有的函数都可以访问。

类的成员,不管使用哪种访问修饰符,都必须通过类的对象进行访问。即使是在类的成员函数内部,访问的数据也是通过类对象进行的,每个成员函数默认的第一个形参为this指针,其中访问的数据成员全部是由“this->”这种方式进行的,只是默认情况下都省略了而已。C++的访问修饰符的作用是以类为单位,而不是以对象为单位。通俗的讲,同类的对象间可以“互相访问”对方的数据成员,只不过访问途径不是直接访问。
我们知道,可以通过一个类对象调用public成员函数,然后通过这个成员函数就可以访问和操作该对象的所有数据成员以及成员函数,不管它们是用什么访问修饰符修饰。如果我们的类中有一个public成员函数,接受一个相同类对象的引用或者指针为形参,那么我们通过一个类对象调用一个public成员函数访问同类其他对象的所有数据成员。咋一想,好像有点问题,一个类对象的数据成员(即使是private成员)能被另外一个类对象的成员函数访问?但是仔细想想,其实是可以理解的。因为类的成员函数是共享的,访问数据成员都是通过类对象来进行的。

Read Full Post »

有关构造函数

1. 默认构造函数:只有当一个类没有定义构造函数的时候,编译器才会自动生成一个默认构造构造函数。
如下代码是编译会有问题:

class Myclass
{
public:
Myclass(int i);//自定义构造函数
private:
int m_data;
};
//因为存在自定义构造函数,所以编译器不会生成一个默认构造函数Myclass(){},所以会出错!
Myclass A;//error
//所以你最好人工添加一个默认构造函数Myclass(){}

2. 复制构造函数和赋值操作符
如果你的类中没有定义这两个东东,那么系统也会给你合成它们,他们的原型如下:

Myclass(const Myclass &);//也可以不要const限定符
Myclass & operator=(const Myclass &);//也可以不要const限定符

那什么时候会调用复制构造函数呢?
如下:

Myclass A;
Myclass B(A);//调用复制构造函数
Myclass C=A;//这里不是调用赋值操作符,是调用复制构造函数
//当Myclass类型的对象传递给一个函数,或者函数返回该类型的对象的时候,会隐式调用复制构造函数,如:
Myclass Func(Myclass T);
//那么调用这个函数的时候,会调用两次复制构造函数,但是构造返回的对象是临时对象,调用过程结束,那个对象也就消亡了。
//以下是调用赋值操作符,看看与上面的不同吧!
Myclass A(1),B;
B=A;//调用赋值操作符

如果你要你的类不允许复制构造,那么你可以将复制构造函数定义成private。

Read Full Post »

placement new释疑

"placement new"? 它到底是什么东东呀?我也是最近几天才听说,看来对于C++我还差很远呀!placement new 是重载operator new的一个标准、全局的版本,它不能被自定义的版本代替(不像普通的operator new和operator delete能够被替换成用户自定义的版本)。
它的原型如下: void *operator new( size_t, void *p ) throw()  { return p; }
首先我们区分下几个容易混淆的关键词:new、operator new、placement new new和delete操作符我们应该都用过,它们是对堆中的内存进行申请和释放,而这两个都是不能被重载的。要实现不同的内存分配行为,需要重载operator new,而不是new和delete。
看如下代码: class MyClass {…}; MyClass * p=new MyClass;
这里的new实际上是执行如下3个过程:

Read Full Post »

Older Posts »