关于C++的一些总结

1,C++中的空类,默认产生哪些类成员函数?

 

class Empty

{

  public:

  Empty(); // 缺省构造函数

  Empty( const Empty& ); // 拷贝构造函数

  ~Empty(); // 析构函数

  Empty& operator=( const Empty& ); // 赋值运算符

  Empty* operator&(); // 取址运算符

  const Empty* operator&() const; // 取址运算符 const

};

 

2,关于类型转换

 

static_cast、dynamic_cast、reinterpret_cast、和const_cast

关于强制类型转换的问题,很多书都讨论过,写的最详细的是C++ 之父的《C++ 的设计和演化》。最好的解决方法就是不要使用C风格的强制类型转换,而是使用标准C++的类型转换符:static_cast, dynamic_cast。标准C++中有四个类型转换符:static_cast、dynamic_cast、reinterpret_cast、和const_cast。下面对它们一一进行介绍。

 

static_cast

 

用法:static_cast < type-id > ( expression )

 

该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性。它主要有如下几种用法: 

用于类层次结构中基类和子类之间指针或引用的转换。进行上行转换(把子类的指针或引用转换成基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。

用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。

把空指针转换成目标类型的空指针。

把任何类型的表达式转换成void类型。

注意:static_cast不能转换掉expression的const、volitale、或者__unaligned属性。

 

dynamic_cast

 

用法:dynamic_cast < type-id > ( expression )

 

该 运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针,如果type-id是一个引用,那么expression也必须是一个 引用。

 

dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。

 

在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。

class B{

 

public:

 

int m_iNum;

 

virtual void foo();

 

};

 

class D:public B{

 

public:

 

char *m_szName[100];

 

};

 

 

 

void func(B *pb){

 

D *pd1 = static_cast<D *>(pb);

 

D *pd2 = dynamic_cast<D *>(pb);

 

}

 

在 上面的代码段中,如果pb指向一个D类型的对象,pd1和pd2是一样的,并且对这两个指针执行D类型的任何操作都是安全的;但是,如果pb指向的是一个 B类型的对象,那么pd1将是一个指向该对象的指针,对它进行D类型的操作将是不安全的(如访问m_szName),而pd2将是一个空指针。另外要注 意:B要有虚函数,否则会编译出错;static_cast则没有这个限制。这是由于运行时类型检查需要运行时类型信息,而这个信息存储在类的虚函数表 (关于虚函数表的概念,详细可见<Inside c++ object model>)中,只有定义了虚函数的类才有虚函数表,没有定义虚函数的类是没有虚函数表的。

 

另外,dynamic_cast还支持交叉转换(cross cast)。如下代码所示。

class A{

 

public:

 

int m_iNum;

 

virtual void f(){}

 

};

 

 

 

class B:public A{

 

};

 

 

 

class D:public A{

 

};

 

 

 

void foo(){

 

B *pb = new B;

 

pb->m_iNum = 100;

 

D *pd1 = static_cast<D *>(pb); //copile error

 

D *pd2 = dynamic_cast<D *>(pb); //pd2 is NULL

 

delete pb;

 

}

 

在函数foo中,使用static_cast进行转换是不被允许的,将在编译时出错;而使用 dynamic_cast的转换则是允许的,结果是空指针。

 

reinpreter_cast

 

用法:reinpreter_cast<type-id> (expression)

 

type-id必须是一个指针、引用、算术类型、函数指针或者成员指针。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。

 

该运算符的用法比较多。

 

const_cast

 

用法:const_cast<type_id> (expression)

 

该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

 

常量指针被转化成非常量指针,并且仍然指向原来的对象;常量引用被转换成非常量引用,并且仍然指向原来的对象;常量对象被转换成非常量对象。

 

Voiatile和const类试。举如下一例:

class B{

 

public:

 

int m_iNum;

 

}

 

void foo(){

 

const B b1;

 

b1.m_iNum = 100; //comile error

 

B b2 = const_cast<B>(b1);

 

b2. m_iNum = 200; //fine

}

 

上面的代码编译时会报错,因为b1是一个常量对象,不能对它进行改变;使用const_cast把它转换成一个常量对象,就可以对它的数据成员任意改变。注意:b1和b2是两个不同的对象。

 

3,关于派生类的构造函数,赋值函数等的说明


a, 派生类的构造函数应在其初始化表里调用基类的构造函数。

b, 基类与派生类的析构函数应该为虚(即加virtual关键字)。

c, 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。例如

 

class Base

{

public:

Base&operate=(const Base&other);

private:

int m_i,m_j,m_k;

};

 

class Derived:public Base

{

public:

Derived&operate=(const Derived&other);

private:

int m_x,m_y,m_z;

};

Derived&Derived::operate=(const Derived&other

{

//(1)检查自赋值

if(this==&other)

return*this;

//(2)对基类的数据成员重新赋值

Base::operate=(other);//因为不能直接操作私有数据成员

//(3)对派生类的数据成员赋值

m_x=other.m_x;

m_y=other.m_y;

m_z=other.m_z;

//(4)返回本对象的引用

return*this;

}

 

 4,关于指针,数组,*,[],优先级结合问题

声明以下变量:

a) 一个指向有10个整型数数组的指针( A pointer to an array of 10 integers)
b) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)

c)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

解答:

a) int (*p)[10]; // A pointer to an array of 10 integers
b) int (*p)(int); // A pointer to a function a that takes an integer argument and returns an integer
c) int (*p[10])(int);

解析:

首先,int * p[10]与int (*p)[10]的区别。由于[]的结合优先级高于*,在 int * p[10]中致使p先与[]结合,表明p是一个数组名,其内容是int*。而在int (*p)[10]中p先与*结合表明p是一个指针,其指向一个无名数组,p值改变后,该数组将丢失。b,c的分析同类。

 

5,为什么不能有虚拟构造函数?   
虚拟调用是一种能够在给定信息不完全(given   partial   information)的情况下工作 
的机制。特别地,虚拟允许我们调用某个函数,对于这个函数,仅仅知道它的接口,而不知 
道具体的对象类型。但是要建立一个对象,你必须拥有完全的信息。特别地,你需要知道要 
建立的对象的具体类型。因此,对构造函数的调用不可能是虚拟的。拷贝构造函数本质和构造函数一样。

6,0字节问题

c++空类对象占用空间非0,一种解释是:方便寻址。假想两个连续定义的空类对象,如果为其分配0空间,将导致两个对象的地址相同,此时如对两空类对象分别寻址,寻得的地址亦相同,显然这会产生语义上的错误,因为不知该地址上的对象是何类型。对于malloc(0)这样的语句返回问题,则依赖于实现,C99标准并未定义实现,它可能返回一个NULL,也可能返回一个有效地址,有效的地址不代表有效的空间,返回有效地址,其有0字节空间,也是可行的。内存分配操作是建立在与操作对应的数据结构基础之上,只要数据结构定义合理并操作得当,如何处理0字节空间不是问题。

7,关于可重入与线程安全

       若一个程序子程序可以安全的被并行执行,则称其为可重入(reentrant或re-entrant)的;即,当该子程序正在运行时,可以再次进入并执行它。若一个函数是可重入的,则该函数:

  不能含有静态(全局)非常量数据。 不能返回静态(全局)非常量数据的地址。 只能处理由调用者提供的数据。 不能依赖于单实例模式资源的锁。 不能调用不可重入的函数。 多'用户/对象/进程优先级'以及多进程一般会使得对可重入代码的控制变得复杂。同时,IO代码通常不是可重入的,因为他们依赖于像磁盘这样共享的、单独的资源。

       可重入就是,一个函数没有执行完成,由于外部因素或内部调用,又一次进入该函数执行。可重入代码,必须保证资源的互不影响的使用,比如全局变量,系统资源等。

      在LINUX设备驱动中 关于可重入代码:

  简单介绍,因为驱动能够被多个进程调用,互不干扰,这样驱动必须是可重入的。

可重入与线程安全两个概念都关系到函数处理资源的方式。但是,他们有一定的区别。可重入概念会影响函数的外部接口,而线程安全只关心函数的实现。

  可重入最简单的理解就是任何变量都是局部变量。可重入指函数在运行过程中,被中断打断后,待返回时仍然能够正常运行。这就需要在编写代码时注意全局变量和公用资源的使用,同时还需要有编译器的支持。

  大多数情况下,要将不可重入函数改为可重入的,需要修改函数接口,使得所有的数据都通过函数的调用者提供。 要将非线程安全的函数改为线程安全的,则只需要修改函数的实现部分。一般通过加入同步机制以保护共享的资源,使之不会被几个进程同时访问。 因此,相对线程安全来说,可重入性是更基本的特性,它可以保证线程安全:即,所有的可重入函数都是线程安全的,但并非所有的线程安全函数都是可重入的。

  可重入性是函数编程语言的关键特性之一。

8,关于volatile与中断

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
并行设备的硬件寄存器(如:状态寄存器)
一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
多线程应用中被几个任务共享的变量
a)一个参数既可以是const还可以是volatile吗?解释为什么。
 b)一个指针可以是volatile 吗?解释为什么。
c)下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
a)是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。
b)是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
c)这段代码有点变态。这段代码的目的是用来返指针*ptr指向值的平方,但是,由于*ptr指向一个volatile型参数,编译器将产生类似下面的代码:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地该变,因此a和b可能是不同的。结果,这段代码可能返不是你所期望的平方值!正确的代码如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}

 

中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码:

__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf("/nArea = %f", area);
return area;
}

错误如下:
a)ISR 不能返回值。

b)ISR 不能传递参数。

c)在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

d)与第三点一脉相承,printf()经常有重入和性能上的问题。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值