...
{
char str1[] = "abc";
char str2[]="abc";
const char str3[]="abc";
const char str4[]="abc";
char* str5 = "abc";
char* str6="abc";
cout<<(str1==str2)<<endl;//false
cout<<(str3==str4)<<endl;//flase both are arrays in the stack.
cout<<(str5==str6)<<endl;//true , they point to the same area in the constant string area in the memory.
//str5[0] = 'c';//you should not do this, otherwise the program will be abended.
int a[9]=...{0};
cout<<a;//the same as &a ,
cout<<&a;
int* b = new int(9);
cout<<b<<endl;//the address of the new int(9) in the heap area
cout<<&b;//the address of b itself in the stack area
}
指出下面代码的输出,并解释为什么。
main()
{
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
printf("%d,%d",*(a+1),*(ptr-1));
}
输出:2,5
*(a+1)就是a[1],*(ptr-1)就是a[4],执行结果是2,5
&a+1不是首地址+1,系统会认为加一个a数组的偏移,是偏移了一个数组的大小(本例是5个int)
int *ptr=(int *)(&a+1);
则ptr实际是&(a[5]),也就是a+5
原因如下:
&a是数组指针,其类型为 int (*)[5];
而指针加1要根据指针类型加上一定的值,
不同类型的指针+1之后增加的大小不同
a是长度为5的int数组指针,所以要加 5*sizeof(int)
所以ptr实际是a[5]
但是prt与(&a+1)类型是不一样的(这点很重要)
所以prt-1只会减去sizeof(int*)
a,&a的地址是一样的,但意思不一样,a是数组首地址,也就是a[0]的地址,&a是对象(数组)首地址,a+1是数组下一元素的地址,即a[1],&a+1是下一个对象的地址,即a[5].
good website about the C+= http://www.w3sky.com/0/277.html
class
A
...
{
private:
virtual void a()...{cout<<"I am A";};
}
;
class
B:
public
A
...
{
private:
void a()...{cout<<"I am B";};
}
;
你不能重载或重定义父类的PRIVATE函数。
当程序中throw new Object();
的时候,你只能用catch(object*)来抓获异常,
Object a;
throw a;
你就可以用catch(object)或者catch(object&)来抓获异常。。。
throw "dd";
catch(char*)来抓获
String:
如果要比较两个string,忽视他们的大小写,那么可以使用stricmp(strA.c_str(),strB.c_str());
如果cout一个没有重载<<的操作符的对象,不能编译通过
const string& name;如果在类中如此声明成员变量,那么这个变量只能在类外被修改,类中是不能修改的
static类成员永远也不会在类的构造函数初始化。基类数据成员总是在派生类数据成员之前被初始化,所以使用继承时,要把基类的初始化列在成员初始化列表的最前面。
static 类成员,必须在类声明后,进行 A::a;这样的初始化,如果没有,则编译器报错,默认为0.但非STATIC的成员,不进行初始化,结果是不可预测的。。
c++语言标准关于这个问题的阐述非常清楚:当通过基类的指针去删除派生类的对象,而基类又没有虚析构函数时,结果将是不可确定的。在使用sizeof来衡量一个类的大小的时候,一般不要把static成员变量计入。。。即使你的类是用的纯虚函数,你的对象依然会带有一个虚函数表指针
,值得指出的是,在某些类里声明纯虚析构函数很方便。纯虚函数将产生抽象类——不能实例化的类(即不能创建此类型的对象)。有些时候,你想使一个类成为抽象类,但刚好又没有任何纯虚函数。怎么办?因为抽象类是准备被用做基类的,基类必须要有一个虚析构函数,纯虚函数会产生抽象类,所以方法很简单:在想要成为抽象类的类里声明一个纯虚析构函数。
这里是一个例子:
class awov { // awov = "abstract w/o // virtuals" public: virtual ~awov() = 0; // 声明一个纯虚析构函数 };
这个类有一个纯虚函数,所以它是抽象的,而且它有一个虚析构函数,所以不会产生析构函数问题。但这里还有一件事:必须提供纯虚析构函数的定义:
awov::~awov() {} // 纯虚析构函数的定义
这个定义是必需的,因为虚析构函数工作的方式是:最底层的派生类的析构函数最先被调用,然后各个基类的析构函数被调用。这就是说,即使是抽象类,编译器也要产生对~awov的调用,所以要保证为它提供函数体。如果不这么做,链接器就会检测出来,最后还是得回去把它添上。
int i1, i2, i3;
...
(i1 = i2) = i3; // 合法! i2赋给i1
// 然后i3赋给i1!
允许子父类有一样的成员变量和函数,不过如果要调用的话,最好使用类::标识符。
使用这个办法可以阻止一个类被继承
template <class T>
class inheritance_lock {
friend class T;
inheritance_lock() {}
};
class Usable : private virtual inheritance_lock<Usable> { ... };
class DD:public usable{}
DD dd不会成功,他无法获得最base的类的好、构造函数,因为Usable作为BASE的friend,并不能使得它的子类变成BASE的FRIEND。。
对于没有声明相应参数为const的函数来说,传递一个const对象是非法的。这是一个关于const的很简单的规定。
// 正确的赋值运算符 derived& derived::operator=(const derived& rhs) { if (this == &rhs) return *this;
base::operator=(rhs); // 调用this->base::operator= y = rhs.y;
return *this; } 做子类的赋值函数的时候必须显式的进行BASE类的operator=函数的调用
derived& derived::operator=(const derived& rhs) { if (this == &rhs) return *this;
static_cast<base&>(*this) = rhs; // 对*this的base部分 // 调用operator= y = rhs.y;
return *this; }
这段怪异的代码将*this强制转换为base的引用,然后对其转换结果赋值。这里只是对derived对象的base部分赋值。还要注意的重要一点是,转换的是base对象的引用,而不是base对象本身。如果将*this强制转换为base对象,就要导致调用base的拷贝构造函数,创建出来的新对象(见条款m19)就成为了赋值的目标,而*this保持不变。这不是所想要的结果。
Const对象只能使用const的函数,普通对象优先使用非const版本函数,然后是const版本函数。想在一个const函数中修改this的值,1,可以把这个值声明为一个mutable的类型,2其次可以把this 使用const_cast来进行转换
class window { public: string name() const; //
返回窗口名
virtual void display() const; //
绘制窗口内容
};
class windowwithscrollbars: public window { public: virtual void display() const; };
// 一个受“切割问题”困扰的函数 void printnameanddisplay(window w) { cout << w.name(); w.display(); }。printnameanddisplay内部,w的行为就象是一个类window的对象(因为它本身就是一个window的对象),而不管当初传到函数的对象类型是什么。尤其是,printnameanddisplay内部对display的调用总是window::display,而不是windowwithscrollbars::display。
使用了引用就可以避免被切割void printnameanddisplay(const window& w) { cout << w.name(); w.display(); }
这里有个原则,传递参数时候,尽量使用引用,因为调用函数知道如何处理传递的参数,返回值尽量使用值传递,除非调用函数可以控制(delete)返回的参数。
只要有可能,就要避免对一个数字和一个指针类型重载。
如果要避免使用隐式生成的函数,比如赋值函数,你可以把它声明成PRIVATE,然后不定义它。这样如果使用了这个函数的话,链接的时候就会报错
sdm::handle& (* const gethandle)() = // gethandle是指向sdm::gethandle sdm::gethandle;把函数
内联函数中的静态对象常常表现出违反直觉的行为。所以,如果函数中包含静态对象,通常要避免将它声明为内联函数。具体介绍参见条款M26。
分离的关键在于,"对类定义的依赖" 被 "对类声明的依赖" 取代了。所以,为了降低编译依赖性,我们只要知道这么一条就足够了:只要有可能,尽量让头文件不要依赖于别的文件;如果不可能,就借助于类的声明,不要依靠类的定义。其它一切方法都源于这一简单的设计思想。
减少文件间的编译依赖性:
1 ,分离的关键在于,"对类定义的依赖" 被 "对类声明的依赖" 取代了。所以,为了降低编译依赖性,我们只要知道这么一条就足够了:只要有可能,尽量让头文件不要依赖于别的文件;如果不可能,就借助于类的声明,不要依靠类的定义。其它一切方法都源于这一简单的设计思想。
2,如果可以使用对象的引用和指针,就要避免使用对象本身。定义某个类型的引用和指针只会涉及到这个类型的声明。定义此类型的对象则需要类型定义的参与。
3,尽可能使用类的声明,而不使用类的定义
4,不要在头文件中再(通过#include指令)包含其它头文件,除非缺少了它们就不能编译。相反,要一个一个地声明所需要的类,让使用这个头文件的用户自己(通过#include指令)去包含其它的头文件,以使用户代码最终得以通过编译。一些用户会抱怨这样做对他们来说很不方便,但实际上你为他们避免了许多你曾饱受的痛苦。
5,句柄类和协议类分离了接口和实现,这是分离的两种实现方式
当重写父类的非虚函数的时候
class D: public B { public: void mf(); //
隐藏了
B::mf;
参见条款
50
...
};
pB->mf(); //
调用
B::mf
pD->mf(); // 调用D::mf
:任何条件下都要禁止重新定义继承而来的非虚函数。
决不要重新定义继承而来的缺省参数值
。关于私有继承的第一个规则正如你现在所看到的:和公有继承相反,如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Student)转换成基类对象(如Person)。这就是上面的代码中为对象s调用dance会失败的原因。第二个规则是,从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员。
当你觉得一个类直接使用不安全,就可以把它的函数都定义为protected,包括了初始化函数,这个时候,你可以生成一个类私有化继承它,这样就达到了使用它,而有不直接生成的效果。这和上面说的阻止一个类被继承有相同的效果
class Lottery { public: virtual int draw();
...
};
class GraphicalObject { public: virtual int draw();
...
};
class LotterySimulation: public Lottery, public GraphicalObject {
... //
没有声明
draw
};
LotterySimulation *pls = new LotterySimulation;
pls->draw(); // 错误! ---- 二义 pls->Lottery::draw(); // 正确 pls->GraphicalObject::draw(); // 正确
class A{
public:
virtual c(){}
}
Class B{
Private:
C(){}
}
A* a = new B();
a. c()调用的实际上还是很
B.C()所以可以发现实际上B的C限定符是没有意义的。。除非a是关于B的指针。。
当你使用虚拟继承的时候,
1,向虚基类传递构造函数参数。非虚继承时,基类构造函数的参数是由紧临的派生类的成员初始化列表指定的。避免这个问题的一个好办法是:消除对虚基类传递构造函数参数的需要。最简单的做法是避免在这样的类中放入数据成员。这本质上是Java的解决之道:Java中的虚基类(即,"接口")禁止包含数据
2,
假设
A
定义了一个虚成员函数
mf
,
C
重定义了它;
B
和
D
则没有重定义
mf
:
A virtual void mf();
//
/ /
B C virtual void mf();
/ /
D
根据以前的讨论,你会认为下面有二义:
D *pd = new D; pd->mf(); // A::mf
或者
C::mf?
该为
D
的对象调用哪个
mf
呢,是直接从
C
继承的还是间接(通过
B
)从
A
继承的那个呢?答案取决于
B
和
C
如何从
A
继承。具体来说,如果
A
是
B
或
C
的非虚基类,调用具有二义性;但如果
A
是
B
和
C
的虚基类,就可以说
C
中
mf
的重定义优先度高于最初
A
中的定义,因而通过
pd
对
mf
的调用将(无二义地)解析为
C::mf
。如果你坐下来仔细想想,这正是你想要的行为;但需要坐下仔细想想才能弄懂,也确实是一种痛苦。
。注意,生成的析构函数一般是非虚拟的(参见条款14),除非它所在的类是从一个声明了虚析构函数的基类继承而来
class Month {
public:
static const Month Jan() { return 1; }
static const Month Feb() { return 2; } ... static const Month Dec() { return 12; }
int asInt() const //
为了方便,使
Month { return monthNumber; } //
可以被转换为
int
private: Month(int number): monthNumber(number) {}
const int monthNumber; };这个例子只返回MONTH为1到12的对象。注意到Static函数的使用,和singleton的有异曲同工之妙
你绝对无法控制不同被编译单元中非局部静态对象的初始化顺序。
:虽然关于
"
非局部
"
静态对象什么时候被初始化,
C++
几乎没有做过说明;但对于函数中的静态对象(即,
"
局部
"
静态对象)什么时候被初始化,
C++
却
明确指出:它们在函数调用过程中初次碰到对象的定义时被初始化。
class B { public: virtual void f() const; };
class D: public B { public: virtual void f(); };D中的f()实际上并没有重写f(),因为少了const。。。
同样的道理
class G { public: virtual void f() const=0; };
class H: public G { public: virtual void f(){cout<<"DD";} };
编译器可不认为你已经重写了f()函数
。标准算法有for_each(为序列中的每个元素调用某个函数),find(在序列中查找包含某个值的第一个位置 ---- 条款M35展示了它的实现),count_if(计算序列中使得某个判定为真的所有元素的数量),equal(确定两个序列包含的元素的值是否完全相同),search(在一个序列中找出某个子序列的起始位置),copy(拷贝一个序列到另一个),unique(在序列中删除重复值),rotate(旋转序列中的值),sort(对序列中的值排序)。注意这里只是抽取了所有算法中的几个;标准库中还包括其它很多算法。
class Base { public: virtual void f(int x); };
class Derived: public Base { public: virtual void f(double *pd); };
Derived *pd = new Derived; pd->f(10); // 错误!
子类提供的函数特征不同于父类。。所以会把父类的隐藏,但是要注意
Base *pd = new Derived; pd->f(10);却是正确的
class Derived: public Base { public: using Base::f; //
将
Base::f
引入到
// Derived
的空间范围
virtual void f(double *pd); };
Derived *pd = new Derived; pd->f(10); // 正确,调用Base::f