C and C++ Common Knowledge

来源:互联网 发布:淘宝新店提取神器 编辑:程序博客网 时间:2024/05/16 10:24

-1,程序中各段的意义如下:
.text(代码段):用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。
.data(数据段):数据段用来存放可执行文件中已初始化全局变量,也就是存放程序静态分配的变量和全局变量。
.rodata(只读段):该段保存着只读数据,在进程映象中构造不可写的段。
.bss(BSS段):BSS段包含了程序中未初始化全局变量,在内存中 bss段是否全部置零依赖于系统的实现。

0, 深入掌握C++以及灵活分析C++中问题的必备知识: C++对象建立语意学(呵呵,模仿一下候捷的语气)

详细内容见拙著(正在撰写,暂时不能访问,暂时参阅<深度探索C++对象模型> 和<潘凯:C++对象布局及多态实现的探索>系列):
  http://blog.csdn.net/I_Love_CPP/archive/2007/09/07/1775472.aspx

1,阻止对象的生成
方法1:纯虚类
方法2:构造函数申明为protected.

2,阻止对象的派生(模拟Java中的final类)
class A{
 private:
  A(){
  }
 public:
  A(int i){
  }
 };
A a(1);//正确


 class B:public A{
 public:
  B(){//错误
  };
 };
 
B b;//错误
 
3,使用局部类和嵌套类实现类的隐藏(有点类似Java中的内部类)
当希望充分利用OO思想,但是又不想把类暴露给外部时可以用局部类。
int main(int argc, char* argv[])
{
//这个类只是我用来调试用,写的根本见不得人,因此用局部类:)
 class A{
 private:
  A(){
  }
 public:
  A(int i){
  }
 };
 
 A a(1);
 
 system("pause");
 return 0;
}

//类似的,嵌套类是在类中再定义一个类

4,运行时类别识别RTTI技术:
在JAVA中可以用instanceof运算符以及反射机制来进行RTTI,
C++中的RTTI技术呢?
答案是
1)使用使用type_info类,
但因为type_info只有一个私有的拷贝构造函数,
必须通过type_id运算符创建一个临时变量。

SoundableAnimal* pAnimals[2];
pAnimals[0] = new Cat;
pAnimals[1] = new Dog;

for(int i = 0; i < 2; ++i)
{
if(typeid(*pAnimals[i]) == typeid(Cat))//注意,需要把/GR编译开关打开
 pAnimals[i]->Shout();    
delete pAnimals[i];
}

2)在某些要求不要的情况下,可以用dynamic_cast。
当dynamic_cast遇到一个不属于自己的类型时(即不满足is-a关系)就会返回空指针或者抛出异常。

5,return返回一个对象时究竟做了什么?

从一个网友的问题说起:
他写了一个矩阵类,用来实现矩阵的转置:
//Declare
class Matrix 
{
public:
 Matrix();
 virtual ~Matrix();
 
 Matrix(int n,int m);
 Matrix(const Matrix & M2);
 Matrix Transpose() const;
private:
 double *p;
 int m;
 int n;
 static int g_num;
};

//Implements:
Matrix::Matrix(){p = NULL;}

Matrix::~Matrix(){if(p)delete[] p;}

Matrix::Matrix(int nTemp, int mTemp)
{
 n = nTemp;
 m = mTemp;
 p = new double[n*m];
}

Matrix::Matrix(const Matrix & M2)
{
 unsigned int i,j;
 if (M2.p!=0)
 {
  delete []p;//(1)注意这儿,有问题
  m=M2.m;
  n=M2.n;
  p=new double [m*n];
  if (p!=0)
  {
   for (i=0;i<m;++i)
    for (j=0;j<n;++j)
     p[i*n+j]=M2.p[i*n+j];
  }
 }
}

Matrix Matrix::Transpose() const
{
 Matrix temp(n,m);
 unsigned int i,j;
 for (i=0;i<m;++i)
  for (j=0;j<n;++j)
   temp.p[j*m+i]=p[i*n+j];
 return temp;//(2)
}

/////////////////////////////////////////////////////////////////////////////////////////////
程序会出错,指向(1)处。
一般而言大家有个习惯,动态分配内存时先要释放已经分配的内存,而他在默认构造函数中初始化了p = NULL;的,
因此delete NULL看似应该不会出现问题。
但是注意:
1)拷贝构造函数并不会调用默认构造函数;
2)(2)处return 返回一个对象时,编译器会自动生成一个临时对象。只要是返回对象而不是指针或者引用,那么
  此临时对象是必然会创建的。也就是说return temp等价于return Matrix(temp);
  但是,拷贝构造函数中并初始化p,因此p指向了一块任意的内存区,当然调用delete[] p;会出错。
3)在拷贝构造函数中以及所有的构造函数中考虑“释放已经分配的内存”是否必要呢?
  我的答案是完全不必要。因为对象只是在构造函数成功后才存在,那么哪儿有“已经分配的内存“之说呢”。
  因此1)处的delete[] p;应该删掉。程序就正确了。
 
6,构造函数失败会怎样?

从5.的讨论涉及到了一个话题”构造函数成功“,为了讨论的完备性,我问自己”构造函数失败会怎样“?
刚好我读到过这方面的咚咚,我引用Herb Sutter《More Exceptional C++》文中的回答:
1)构造函数正常返回,这种情况对象真是存在;
2)构造函数抛出异常后退出,这种情况下对象根本从来未存在过(因此也不会调用析构函数)。
再没有其它情况了。

7,字节存储顺序

    字节存储顺序是指系统中应用的字符的顺序(在内存中存储16进制场字符是按0xAABBCCDD的顺序还是按照0xDDCCBBAA的字符顺序)。
    测试字符的存储顺序的具体方法是直接把长字符数值1(0x000001)存到内存中,然后检查1在高位还是低位。
    bool little_endian()
{
    unsigned int i = 0x01;
    return(*(unsigned char*) &i == 0x01);
}
 
void cpu_info()
{
    if(little_endian())
      printf("LITTLE ENDIAN");
    else
      printf("BIG ENDIAN");
 
    printf("%d bit architecture/n",sizeof(int) * 8);
}
 
 
 
 
 
 
8.const与volatile:(事物的2个面 )
关键字volatile有什么含意 并给出三个不同的例子。

一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子:
1). 并行设备的硬件寄存器(如:状态寄存器)
2). 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables)
3). 多线程应用中被几个任务共享的变量
8'
1). 一个参数既可以是const还可以是volatile吗?解释为什么。
2). 一个指针可以是volatile 吗?解释为什么。
3). 下面的函数有什么错误:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一个例子是只读的状态寄存器。它是volatile因为它可能被意想不到地改变。它是const因为程序不应该试图去修改它。 (事物的多面性)
2). 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时。
3). 这段代码的目的是用来返指针*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;
}

 
 9.const与mutable (盾与矛)
const:只读
mutable:A mutable data member is a member that is never const, even when
it is the data member of a const object. A mutable member can always be updated, even in a const
member function. <<c++ primer>>
(最终,是矛戳穿了盾...)
 
多问一句:const 变量/对象是在.rodata段吗?
 
 
 
10. const_cast, static_cast,reinterpret_cast和dynamic_cast
const_cast,
as itsname implies, casts away the const-ness of its expression (and also the volatility of a volatile object).
For example:
extern char *string_copy( char* );
const char *pc_str;
char *pc = string_copy( const_cast< char* >( pc_str ));
An attempt to cast away const-ness using either of the other three forms results in a compile-time error.
Similarly, it is a compile-time error to use the const_cast notation to perform a general type conversion.
static_cast,
Any type conversions that the compiler performs implicitly can be made explicit through use of a
static_cast.
reinterpret_cast,
A reinterpret_cast generally performs a low-level reinterpretation of the bit pattern of its operands,
and its correctness in large part depends on the active management of the programmer.
For example, in the following cast
complex<double> *pcom;
char *pc = reinterpret_cast< char* >( pcom );
the programmer must never lose sight of the actual object addressed by pc.
dynamic_cast
The dynamic_cast supports the run-time identification of class objects addressed either by a pointer or
reference.
 
 
如果觉得英文看的不爽,这有一个网友总结还不错:
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是两个不同的对象。
 
 
 
9'. 如果确实想用c-style简易的强制转换在c++中推荐使用新式类型转换符:
// C++ function cast notation
type (expr); 因为这样的话就可以为类type重载()符,进而使用上面的4种显式类型转换符
// C-language cast notation
(type) expr;
原创粉丝点击