黎明的剔透里伸出了你沁清的暧昧

Graphics|EngineDev|GameDev|2D&3D Art

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  60 随笔 :: 1 文章 :: 18 评论 :: 0 Trackbacks
Basics:
1:仔细区别pointers和references
Œ 没有所谓的null reference,一个reference必须总代表某个对象,所以如果你有一个变量是用来指向一个对象的,但是又有可能不指向一个对象的时候你应该用pointers,这样你就可以将pointers设置为null,如果一个变量必须代表一个对象,隐含的意思就是你得设计并不允许将这个变量设置为null,,那么你应该用reference.
char *pc=0;        // 将pointers初始化为null
char &rc=*pc;     //让reference代表null pointers的解引值 这是有害的行为 其结果不可预期
 再来看reference,由于reference必须代表某个对象,因此必须要求reference必须有初值,但是pointers没有这样的限制.因为pointers使用之前要检查其有效性,这样就意味着reference比pointers高效的多.
string &rs;   //一个引用必须有初值
string s("abcd");
sring  &rs=s; //OK!
Ž pointers可以重新被赋值,指向另一个对象,而reference却总是指向它最初获得的那个对象,因此当你遇到不指向任何对象或者在不同的时间指向不同的对象的时候你要选择pointres,当你确定变量总是会代表某个对象,并且一旦代表一个对象就不再改变的时候,应该用reference.
string s1("abc");
string s2("xyz");

string &rs=s1;      //rs代表了s1
string *ps=&s1;   //ps指向了s1

rs=s2;                //rs仍代表s1 但是s1的值却变成了"xyz"而不是原来的"abc"
ps=&s2;             //ps现在指向了s2 ,s1仍是"abc"而没有变化

在实现某些操作符的时候,如果要返回某个对象的时候 应该注意是reference 
vector<int> v(10);
v[5]=10;//vector的赋值操作符返回一个对象 是引用的
如果返回一个pointers 那么调用时候应该是*v[5]=10;

[总结]:当你知道你需要指向某个东西,而且绝不会改变指向其他东西,或者你实现一个操作符而其语法无法由pointers完成,你就该选择reference,其他情况就改采用pointers.

2:最好使用C++转型转型操作符
Œ C++导入了4个新的转型操作符:static_cast<>(),dyname_cast<>(),cons_cast<>(),reinterpret_cast<>()


static_cast<>()基本上和C旧式的转型的威力、意思以及限制相同。没有运行时的类型检查来检查安全性,而且不能够移除表达式的常量性,C++提供了新式转型操作符const_
cast<>()

Žconst_cast<>()来改变表达式的常量性或者变易性,而且是告诉编译器打算唯一的改变某物的常量性或变易性
calss Widge{
};
class spec:public Widge{
};
void updata(spec *psw);
spec sw;
const spec &csw=sw;        //csw是一个代表了sw的reference 而且是一个const对象
updata(&csw);                //error!! 能将一个const spec*参数传递给一个需要spec*的函数
updata(const(spec*)<&csw>);     //OK!!&csw的常量性被const_cast<>()移除

 dynamic_cast<type_id>(expression);用来执行继承体系中得"安全向下转型或者跨系转型"
type_id必须是类的指针、类的引用或者void*
在用于类层次间向上转换时,其效果与static_cast<>()一样
在用于类层次间向下转换时,其具有类型检查的功能,比static_cast安全,牵涉到类型检查就有运行时类型信息检查,因为运行时类型信息存储在虚函数表中,所以智能用于有虚函数的类型身上,如果你想为一个不涉及继承机制的类型转换执行类型转换你可以使用static_cast<>(). dyanmic_cast<>()也不能改变类型的常量性.
Widge *pw;
updata(dynamic_cast<spec*>(pw));   //ok!!对于pointers如果转型失败奖会返回一个null

void updataRefrence(spec &rsw);

updataReference(dynamic_cast<spec&>(*pw));   //ok!!对于reference如果转型失败将会返回一个异常(exception)

   reinterpret_cast<>()这个操作符的转型结果几乎总是和编译平台有关,因此reinterpret_cast<>()不具有移植性,其最常用的用途是转换"函数指针"类型,
假设有一个数组存储的都是函数指针,有特定的类型
typedef void (*funcptr) ();     //funcptrs是指针 指向某个函数

funcptr funcptrarray[10];     //funcptrarray是个数组内 含有10funcptrs
假设由于某个原因,你希望 将以下函数的一个指针房间funcptrarray中
int dosomething();
如果没有转型,不可能办到这一点,因为dosomething的类型与funcptrarray所接受类型不同,funcptrarray内各个函数指针所指函数的返回值是void,但是dosomething函数的返回类型是int,
funcptrarrat[0]=&dosomething;   //error!!类型不符

funcptrarray[0]=reinterpret_cast<funcptr>(&dosomething);  //ok!!
除非你逼不得已,应该避免将 函数指针转型,这样可能会导致不正确的结果.

3:绝对不要以多态(polymorphicall)方式处理数组


Œ
For an example:
calss BST{
};
calss BalanceBST:public BST{

};
假设BST和balanceBST都内含ints
现在有一个函数来打印BST&数组中每一个BST的内容:
void printBSTArray(ostream &s,const BST array[],int nun){
      for(int i=0;i<num;i++)
       s<<array[i];
  }

BST BSTarray[10];
printBSTArray(cout,BSTarray,10);  //OK!!
内部细节:打印函数中的array[i],这个其实是一个"指针算术表达式",其实质是*(array+i); array 这个数组名其实是一个指针,指向数组的始处,现在来考虑array所指的内存
与array+i所指的内存之间的距离有多远?the answer is: i*sizeof(数组中的对象),why:array[0]和array[i]之间有i个对象,为了嫩那个让编译器所产生的代码能够正确走访整个数组,编译器必须有能力决定数组中的对象大小,这很容易,编译器也能做到,because参数array被声明为"类型为BST"的数组,so数组中的每个元素都必然是BST对象,故array和array+i之间的距离一定是i*sizeof(BST);

而如果你把打印函数交给一个由继承类:balanceBST对象组成的数组,你得编译器就会被误导,这种情况下它仍假设数组中每一个元素的大小是BST的大小,这 可能 是在声明函数时参数设定为BSTarray,这在编译时交给符号表来分配内存有关<猜想>,一般继承类都比基类有更多的data members,so继承类的对象通常都要比基类对象要大,所以对于继承类对象产生的数组对象来执行打印函数,将会发生不可预期的错误.

 
如果你尝试通过一个基类指针,删除一个由继承类组成的数组,情况如下:
void deletearray(ostream &logstrea,BST ARRAY[]){
      logstream<<""delete array"<<static_cast<void*>(array)<<endl;
     delete [] array;
 }

balanceBST *balan_cearray=new balanceBST[50];
delatearray[cout,balan_cearray);
这其中也一样含有一个"指针算术表达式",虽然你没看到,当数组被删除的时候,数组中的每一个元素的析构函数都会被调用,所以编译器会看到如下delete [] array;的时候将产生下述代码:
for(int i=the number of elements in the array-1;i>=0;--i){//将*array中的对象以其构造函数的相反顺序加以析构
    array[i].BST::~BST();                                               //调用array[i]的析构函数
 }

如果你这么一写将是一个行为的错误的循环;如果编译器产生类似代码,当然同样是一个行为的错误,C++规范中规定:通过base calss 指针删除一个由deriver class objects构成的数组,其结果未定义。所谓的未定义就是:执行之后会产生苦恼的错误,error!!,


[总结]:多态和指针算术不能混用,数组对象几乎总是涉及指针的算术运算,所以数组和多态不要混用;注意,如果你涉及类的时候避免让一个具体类继承自一个具体类,你就不太可能犯这种错误。


4:非必要不提供 default constructor

所谓的defualt constructor就是指一个没有参数的构造函数,不管是编译器隐式生成还是程序员显示的声明,如果没有定义构造函数,编译器会在在四种情况为类生成default constructor.

default constructor的意思是没有任何外来信息的情况将对象初始化,但是并非所有对象都落入这样的分类,有许多对象,如果没有外来信息,就没有办法执行一个完全的初始化动作,在一个完美的世界中,凡是可以"合理地从无到有生成对象"的class,都应该内含default constructor,而"必须有某些外来信息才能生成对象的"class,则不必拥有default constructor.换句话说也就是,一个没有含有default constuctor 函数的类,将会有某些限制.
for an example:一个针对公司仪器而设计的类
 1 #include <iostream>
 2 using namespace std;
 3 class EQ{ public:
 4       EQ(int id);
 5       void printf(){
 6            cout<<"printf eq"<<endl;
 7       }
 8 };
 9 
10 int main(){
11     EQ eq[10];
12     eq[1].printf();
13     return 0;
14 }
编译这个代码,编译器将产生错误:
error C2512: 'EQ' : no appropriate default constructor available
error C2248: 'printf' : cannot access private member declared in class 'EQ'

由于eq缺乏defalut constructor,其运行可能在3种情况下出现:
第一种:产生数组的时候,就如上的代码,一般而言没有任何方法为数组中的对象指定construtor自变量,所以几乎不可能产生一个由EQ对象构成的数组
           有三种方法可以侧面的解决这个限制:
            第一种:使用non-heap数组;
int id1,id2,id3,......id10;
EQ eq[]={
    EQ(id1),
    EQ(ID2),
......
   EQ[10]};
 1 #include <iostream>
 2 using namespace std;
 3 class EQ{
 4 public:
 5     EQ(int id){}
 6       void printf(){
 7            cout<<"printf eq"<<endl;
 8       }
 9 private:
10 
11 };
12 
13 int main(){
14     int id1,id2;
15     EQ eq[]={EQ(id1),EQ(id2)};
16     eq[1].printf();
17     return 0;
18 }
这样eq[1]就可以执行printf函数,这是因为constructor获得了必要地自变量
但是这种方法不能延伸到heap数组,更一般化的方法是:使用指针数组,而非对象数组
posted on 2011-11-17 00:30 情绝格调(fresmaster) 阅读(373) 评论(0)  编辑 收藏 引用 所属分类: Coding

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理