C++ 概念易错点
typedef声明不属于类的成员
class Account { typedef double Money; // ... private: static Money _interestRate; static Money initInterest(); }; // Money 必须用 Account:: 限定修饰 Account::Money Account::_interestRate = initInterest();
在类体外的类成员定义中、只有被定义的成员名字之后的程序文本、才属于该类域、而不能加任何类成员限定符、
比如这个例子、由于Account::_interestRat引用了类成员、所以后面的initIntererst()不需要再写上成员限定符::、那么为什么
Account::Money之后不能直接也把_interestRate的成员限定符取消掉呢?答案狠简单、因为Money并不是Account的类成员、它
既不是成员函数、也不是一个数据成员、它只是在Account类定义里面声名的一个typedef、所以直到Account::_interestRate开始、
后面的类成员才不需要接上成员限定符::
========================================================================
使用类默认构造函数声明类对象时、注意不要声明返回类型为类的函数!
比如:
class Test{
Test(){} // 显示的声明默认的构造函数
Test(int val){} // 用户自定义的构造函数
};
int val = 3;
Test func(); //错误!声明成了返回类型为Test函数名为func的函数!
Test func(val); //正确!可以这么搞
Test func; // 正确!如果一定要使用默认构造函数声明、就把括号去掉!
========================================================================
构造函数不能声明为const或者volatile
构造函数不能声明为const或者volatile、但这无阻于const类对象调用构造函数、为什么会这样呢、这样不是与const类对象只能调用const成员函数的规则相冲突了吗、其实不是!因为const的常量性是当构造函数已经执行完毕才建立起来、在调用析构函数时常量性就消失、也就是说、const常量性不作用于构造和析构函数、这也是为什么构造函数不能声明为const或者volatile的原因了、
========================================================================
C++要求、赋值=、下标[]、 调用()、 和 成员访问箭头-> 操作符、必须被定义为类成员操作符
类成员操作符就是在类定义里面定义的操作定义、比如类String和c风格字符串是否相等
str == "c_str";
这个是类成员操作符定义
class String { public: bool operator==( const char * ) const; };
这个是名字空间成员定义、也就是普通的函数定义、非类的成员函数
bool operator==(String&, const char *);
如果是以上4个以外的操作符、都可以使用名字空间成员定义、但只要是任何把这4种操作符定义为名字空间成员的定义都会被标记为编译时刻错误!
========================================================================
派生类虽然继承了基类(同样也包括间接基类)的数据成员和成员函数、但不继承基类的构造函数!
========================================================================
重载操作符只局限于类与枚举类型、不能重载内置类型、不能改变操作符的默认优先级、只能在C++预定义操作符集的操作符才能重载!
========================================================================
派生类(例如成员函数)可以访问基类的protected数据成员、但派生类的对象不能访问基类对象的protected数据成员!也就是说派生类只能访问自己本身对象的基类protected数据成员、而不能访问其它派生类对象的基类的protected数据成员
(这点区别于同类对象的访问权限、同类对象在成员函数内可以用对象名调用其它同类对象任何权限的类成员而不受限制)
class Query { protected: string _loc; }; class NameQuery : public Query { public: bool compare(const Query*); }; bool NameQuery:: compare( const Query *pquery ) { // ok: 自己的 Query 子对象的 protected 成员 int myMatches = _loc.size(); // 错误: 没有 "直接访问另一个独立的 Query // 对象的 protected 成员" 的权利 int itsMatches = pquery->_loc.size(); return myMatches == itsMatches; } };
_loc.size()是对的、因为是在派生类成员函数里调用本基类的protected数据成员_loc、
而pquery->_loc.size()是错的、不能使用任何派生类对象来直接调用基类的protected数据成员
========================================================================
当派生类下含有与基类同名的成员函数、并不会与基类成员函数达成重载、而是直接覆盖掉基类的成员函数!如果迩想重载的话必须重写基类的同名函数或者使用using classname::meber_method_name、(但前提是该方法基类未声明成private)
========================================================================
派生类构造函数只能合法地在其成员初始化表中调用其直接基类的构造函数
这里注意这里的三个关键字、派生类构造函数初始化列表、调用、直接基类以及它的构造函数
class Base { public: Base(int ival){} void test_m(){} }; class P1 : public Base{ public: P1() : Base(3) {} P1(int ival) : Base(3) {} void test_p1(){} }; class P1p1 : public P1 { public:
P1p1() : P1() {} // 合法的、因为P1是P1p1的直接基类
P1p1(int ival) : Base(3) {} //非法的、因为Base并类P1p1的直接基类
P1p1(double dval) {Base(3);} //合法的、因为不是在初始化列表中调用
void test_p1p1() { Base(4); //合法的、可以在非构造函数内调用非直接基类的构造函数 } };
========================================================================
派生类初始化的顺序、
1、直接基类的构造函数
2、数据成员的构造函数
3、派生类自身的构造函数
class Base{ }; class Boy : public Base{ public: Boy()
// : Base() { i = 3; } private: string str; int i; };
首先是Base的初始化、当然、这里是调用Base的缺省初始化函数、
然后就是调用数据成员的初始化构造函数、这里是str和i、因为i没有构造函数就不用执行了、只执行str的初始构造函数
然后就是派生类自己的构造函数、这里要做的事情就是把3赋值给i
这里值得注意的是一旦基类没有提供缺省构造函数、那么以上定义便是错的
class Base{ public: Base(int ival){} }; class Boy : public Base{ public: Boy() //错误!没有Base() { i = 3; } private: string str; int i; };
因为派生类的第一步是执行基类的构造函数、如果不在初始化列表中显示指定基类构造函数的执行方式、那么初始化列表都隐式来调用基类的缺省构造函数、显然因为没有缺省构造函数的话、这一步是错误的、必需显示的在初始化列表中声明蕨类构造函数所需要的参数、如Boy() : Base(3) { i = 3;}、这样便正确了、
同样的如果数据成员string若缺乏缺省构造函数的话、也是错误的、但这里使用的是标准库的string类型、因为有默认的缺省构造函数所以不会报错
========================================================================
虚拟成员函数的缺省参数是在编译时刻根据被调用函数的对象的类型决定的、而调用的真正实例是在运行时刻根据对象指针指向的实际类型决定的
此话怎讲呢、如
// test_default_parameter_virtual_method.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <iostream> using namespace std; class base { public: virtual int foo( int ival = 1024 ) { cout << "base::foo() -- ival: " << ival << endl; return ival; } }; class derived : public base { public: virtual int foo( int ival = 2048 ) { cout << "derived::foo() -- ival: " << ival << endl; return ival; } }; int _tmain(int argc, _TCHAR* argv[]) { derived *pd = new derived; base *pb = pd; int val = pb->foo(); val = pd->foo(); char wait; cin >> wait; return 0; }
输出结果为
derived::foo() -- ival: 1024 derived::foo() -- ival: 2048
狠明显、无论是通过pb还是pd来调用foo()方法、都是派生类derived的方法实例、但是ival值却是不一样的、区别就在于两者的决定时间是不一样的、缺省参数的值在程序编译时就已经确定下来了、而调用的方法实例却是在运行时刻才决定的、更确切的说是缺省参数是根据编译时刻调用函数的对象类型决定的、pb类型为base的指针类型、当然是指向base类、所以缺省实参选用base的值、而当调用方法实例时、则是根据运行时刻对象指针所指的实际类型来决定的、pb的指针类型是base指针类型、但实际指向的对象却是derived类型的空间、所以自然调用方法实例是使用了derived所提供的方法了、所以就出现这现在这种情况、使用基类的缺省参数而使用派生类的方法实例、囧
========================================================================
即使指针所指的实际对象是可以赋值给相应对象的、但那也是在运行时刻才知道实际对象的类型、因此在编译时刻会以当前指针的类型为准
#include "stdafx.h" #include <typeinfo> #include <iostream> using namespace std; class X { public: virtual ~X(){} }; class A { public: virtual ~A(){} }; class B : public A {}; class C : public B {}; class D : public X, public C {}; int _tmain(int argc, _TCHAR* argv[]) { X *px = new D; A& ra = *px; //这个是错误的、 cout << typeid( ra ).name() << endl; char wait; cin >> wait; return 0; }
因为指向类X对象无法被一个类A的对象所引用、因为类X和A不是派生关系、虽然px指针实际指向的对象是类D、D对象是可以被A所引用的、因为D是A的派生类、但这个实际指向类型为类D实际上是程序运行时才知道的、在编译时刻、此时编译器只知道px是一个指向X类的指针、所以这句会造成编译时刻错误而无法通过编译、
如果一定要把*px的地址给类的引用ra的话、请显示转化px的类型为A或者D、或者B、C都可以啦、因为D皆为ABC的派生类、向类层次上转换永远是安全的、这样一来、在编译时刻就告诉编译器px所指向的实际类型了、所以编译通过、程序成功运行
#include "stdafx.h" #include <typeinfo> #include <iostream> using namespace std; class X { public: virtual ~X(){} }; class A { public: virtual ~A(){} }; class B : public A {}; class C : public B {}; class D : public X, public C {}; int _tmain(int argc, _TCHAR* argv[]) { X *px = new D; // A& ra = *px; A& ra = *dynamic_cast<A*>(px); // 正确! cout << typeid( ra ).name() << endl; return 0; }
========================================================================