索引:
构造函数, 构造函数的初始化语法, (virtual)析构函数,inline函数, 私有构造函数和私有析构函数.
重载操作符operator,
构造函数, 构造函数的初始化语法, (virtual)析构函数,inline函数, 私有构造函数和私有析构函数.
重载操作符operator,
类的访问标示(public/private/protected), 友元
名空间namespace.
类的const, static成员
「引用」类型的类成员
String类的重载操作符实现:
第3.2: 重载操作符的左/右操作数,返回值,非类的成员的操作符
对于类的双目操作符,例如string的对象 str=="cpp",实际是str.operator=("cpp"),即左操作数是调用者,右操作数是参数,符号左右对调写作"cpp"==str则会出错;
## 如果重载操作符是类成员,则可以通过this指针调用 左操作数 str;
## C++要求 赋值=,下标[],调用()和成员访问箭头->,操作符必须被定义为类成员, 否则编译期错误;
对于 "非类的成员的"重载操作符 ,如何定义,调用? 第1/2个参数分别表示操作符的左/右值,
bool operator==( const String &str1, const String &str2 )
{
if ( str1.size() != str2.size() )
return false;
return strcmp( str1.c_str(), str2.c_str() ) ? false : true;
}
(1)private成员:
(3)public成员:
第4.2: 类的继承后成员属性变化。
private 属性不能够被继承。
使用 public继承, 基类中的protected和public属性不发生改变;
使用 protected继承,基类的protected和public属性在派生类中变为protected;
第5.2:成员的初始化顺序:
顺序是按照Thing类声明中的顺序,而不是构造函数中赋值的顺序;
第6.1:函数的重载(区分重写):
"重写" override:也叫覆盖,指virtual函数在继承中的定义改写,返回值和形参在过程中保持一致;virtual关键字仅能在函数声明中出现,(纯)虚函数的定义句中不能带有virtual关键字;
"重载" overload:是同域内的同名函数可由形参表区分(返回值可以不同,且仅返回值不能确定重载);
"隐藏"是非虚函数在继承中的定义改写,返回值和形参可以不一致.产生覆盖的函数仍是"静态绑定",基类指针不能调用子类函数;
第7:类设计的健壮性建议:
1.如果复制构造函数的代码很困难写出,为了避免用户调用"编译器合成的复制构造函数",可以把此函数写为private;
2.(纯)虚函数写为private;
3.构造函数/析构函数写为private,这样可以...
(1)防止此类被继承,被当做基类;
(2)构造函数声明为virtual,编译出错,构造函数执行时还未形成vtable;
5.函数形参使用const,例如void func(const int*)或void func(const int&),防止函数内意外修改实参;或者类的某成员函数声明为const,"void func() const;",则此函数不能修改类的数据成员,否则编译器报错;
下面的代码可通过:
void func(const int*);
int *ptr = NULL;
func(ptr); //正常调用
下面的代码不可通过:
void func(char*);
const char* prt = "hello"; //字符串常量
func(prt); //调用出错
第1.1: 复制构造函数&构造函数
定义格式:Thing::Thing(Thing&),Thing::Thing(),
构造函数 被隐式调用 的两种情况:(1)void func(Thing); (2)Thing a; 前者按值传递参数,进行实参的复制,会调用复制构造函数,后者会调用默认构造函数;
编译器自动合成的复制构造函数,仅仅简单把成员复制,如果类的成员中有指针,则会出现意外的问题:两个指针指向同一个地址,这种带有"指针"成员的类要自己完成代码(而不是用编译器合成的);
在书写一个类的代码时,如果实现复制构造函数很困难,但为了代码的健壮性仍要防止用户无意中调用到复制构造函数,则应在基类中添加一个"private的复制构造函数",这样用户一旦"无意中"调用了复制构造函数,编译器会报错;
(*)上句中的"无意中"指如下几种调用:
Base b; Base b_copy(b); //copy-constructor
构造函数的初始化语法,以及什么时候应该使用此语法:
假设类Thing有成员String Thing::name, int Thing::value,则Thing类的初始化语法的构造函数如下:
Thing::Thing(String& s, int v)
定义格式:Thing::Thing(Thing&),Thing::Thing(),
构造函数 被隐式调用 的两种情况:(1)void func(Thing); (2)Thing a; 前者按值传递参数,进行实参的复制,会调用复制构造函数,后者会调用默认构造函数;
编译器自动合成的复制构造函数,仅仅简单把成员复制,如果类的成员中有指针,则会出现意外的问题:两个指针指向同一个地址,这种带有"指针"成员的类要自己完成代码(而不是用编译器合成的);
在书写一个类的代码时,如果实现复制构造函数很困难,但为了代码的健壮性仍要防止用户无意中调用到复制构造函数,则应在基类中添加一个"private的复制构造函数",这样用户一旦"无意中"调用了复制构造函数,编译器会报错;
(*)上句中的"无意中"指如下几种调用:
Base b; Base b_copy(b); //copy-constructor
构造函数的初始化语法,以及什么时候应该使用此语法:
假设类Thing有成员String Thing::name, int Thing::value,则Thing类的初始化语法的构造函数如下:
Thing::Thing(String& s, int v)
:name(s), value(v)
{
//...函数体
}
使用初始化语法带来的性能提升,比如上面的Thing类的构造函数,如果不使用初始化语法,而是在函数体内对name成员进行赋值,这样做相当于调用了两次String类的构造函数,先是String(),然后是String(String&),即先调用默认构造,然后调用复制构造,
使用初始化语法带来的性能提升,比如上面的Thing类的构造函数,如果不使用初始化语法,而是在函数体内对name成员进行赋值,这样做相当于调用了两次String类的构造函数,先是String(),然后是String(String&),即先调用默认构造,然后调用复制构造,
##
但是对于
内建类型
的成员,"初始化语法"不会带来更高的效率.
##
类型为"引用"的成员:由于"引用"的值不可改变,故"char& ref"这样的定义语句会报错,故该类型的成员必须使用初始化语法来初始化;
第1.3: 私有的构造函数/析构函数 ?
私有的"默认构造函数"在语法上成立, 可以作为一种策略, 禁止用户调用某个类的"默认构造函数";
(1) 私有的"默认构造函数", 将不能以"AClass a"的方式创建实例, 也不能以"AClass* p = new AClass()"的方式创建;
(2) 私有的"默认构造函数", 这个类的派生类无法实例化, why?
##派生类实例化, 会调用基类的"默认构造函数",,,
(3) 如果"私有析构函数"... 编译可以通过, 但调用到"私有析构函数", 将会按照private成员函数的规则.
##这个类只能在堆上创建实例, 如果以"AClass a"的方式在栈上创建, a生命周期结束时将调用析构函数~AClass(), 调用私有函数, 报错.
##如果"AClass* p = new AClass()"的方式创建, 在执行"delete p"时, 也会调用"私有的析构", error;
class CTest; //假设CTest有私有的析构函数; void CTest::Destroy(){ //Destroy函数public delete this; } int main(){ CTest *pa = new CTest(); // 不报错 delete(pa); // 报错, 相当于main调用了~CTest() pa->Destroy(); // OK CTest b; // 报错, b自动释放时会调用私有析构 } |
第2.1: const/static
const/static成员的初始化?
const 的函数
返回值/
型参.
answer:
(1) const/static类型的成员的初始化,
const成员只能用"初始化语法"来初始化, 比如初始化const int A::value的代码如下:
A::A(int v): m_value(v)
{
// const成员在初始化列表赋值
// 普通成员在构造函数内赋值
}
static静态成员的初始化, 不应在*.H文件中, 而在cpp文件中, 就像定义全局变量一样:
static double base::s_rate = 1.2;
(2) 引用类型的类成员的初始化,类似const成员,需要用初始化列表。
(2) void CThing::Add() const { ... }
// 表示这个Add()不会改变类成员的值, 如果改变, 编译器报错, 这是一种依靠编译器自我检查代码的习惯;
(3) int func(const int & ref_var)
// 函数形参声明为const, 函数内改变ref_var值的行为都会在编译期报错, 注意const只有是指针或引用时才有意义, int func(const int v)没有意义, 因为形参按值传递, 函数内的变量v只是实参的一个拷贝.
(4) 函数返回值为const, 则"函数调用"不能是左值, 比如 const int func(int), 则语句 func(2) = 10是错误的,
#注意, const AClass a, 一个"不可改变"的类实例, 将不能调用"非const的"成员函数,
#类的const 函数内只能调用const函数, why? const 的类实例, 不能调用非const的函数, why?
第3.1
: 重载操作符示例
:
class String{
// 赋值操作符
String& operator=(const String&);
String& operator=(const char*);
class String{
// 赋值操作符
String& operator=(const String&);
String& operator=(const char*);
// 调用方式str="abc";
// 或a=b=c;
// 比较操作
bool operator==(const String&);
bool operator==(const char*);
// 下标操作
char& operator[](int);
// 比较操作
bool operator==(const String&);
bool operator==(const char*);
// 下标操作
char& operator[](int);
// 调用方式 if( name[n] != 'A' )
// []操作符必须能出现在赋值操作符的左右两边, 为了能在左边出现, 操作符的返回值必须是一个左值.
// 强制转换, 没有返回值
operator double() ;
//
隐式调用: a + 3.13 , 显示调用: a.operator double();
}
#question:为什么重载操作符的返回值一般是const的引用??
#answer: 这样做可以实现这样的语句: a=b=c, 如果写出(a=b)=c这样的语句, 则报错;
String类的重载操作符实现:
inline String& String::operator=(const String& other) { if (this!=&other) { delete[] m_data; if(!other.m_data) m_data=0; else { m_data = new char[strlen(other.m_data)+1]; strcpy(m_data,other.m_data); } } return *this; //返回this的解 } inline String String::operator+(const String &other)const { String newString; if(!other.m_data) newString = *this; else if(!m_data) newString = other; else { newString.m_data = new char[strlen(m_data)+strlen(other.m_data)+1]; strcpy(newString.m_data,m_data); strcat(newString.m_data,other.m_data); } return newString; } inline bool String::operator==(const String &s) { if ( strlen(s.m_data) != strlen(m_data) ) return false; return strcmp(m_data,s.m_data)?false:true; } inline char& String::operator[](unsigned int e) { if (e>=0&&e<=strlen(m_data)) return m_data[e]; } ostream& operator<<(ostream& os,String& str) { os << str.m_data; return os; } |
第3.2: 重载操作符的左/右操作数,返回值,非类的成员的操作符
对于类的双目操作符,例如string的对象 str=="cpp",实际是str.operator=("cpp"),即左操作数是调用者,右操作数是参数,符号左右对调写作"cpp"==str则会出错;
## 如果重载操作符是类成员,则可以通过this指针调用 左操作数 str;
## C++要求 赋值=,下标[],调用()和成员访问箭头->,操作符必须被定义为类成员, 否则编译期错误;
对于 "非类的成员的"重载操作符 ,如何定义,调用? 第1/2个参数分别表示操作符的左/右值,
bool operator==( const String &str1, const String &str2 )
{
if ( str1.size() != str2.size() )
return false;
return strcmp( str1.c_str(), str2.c_str() ) ? false : true;
}
以操作符+为例, 说明重载操作符是成员和非成员的不同:
Sales_item& Sales_item::operator+=(const Sales_item& rhs);
Sales_item Sales_item::operator+(const Sales_item &lhs, const Sales_item &rhs);
Sales_item Sales_item::operator+(const Sales_item &lhs, const Sales_item &rhs);
对于类成员的操作符+, 操作符左边是类实例, 操作符右边是实参, 返回的引用是*this;
对于非类成员的操作符+, 没有this指针, 并且有两个参数, 返回值不是引用类型;
1、赋值(=),下标([ ]),调用(())和成员访问箭头(->)等操作符必须定义为成员,如果定义为非成员的话,程序在编译的时候,会发生错误。
2、和赋值操作符一样,复合赋值操作符通常定义为成员。与赋值操作符不同的是,不一定飞的这样做,如果定义为非成员,编译器不会报告错误。
3、改变对象状态或者与给定类型紧密联系的其他一些操作符,入自增,自减和解引用,通常定义为类成员
4、对称的操作符,for example:算数操作符、相等操作符、关系操作符和位操作符,最好定义为非成员函数。
5、IO操作符必须定义为非成员函数,我们不能将该操作符定义为类的成员函数,否则,左操作数将只能是该类类型的对象。
2、和赋值操作符一样,复合赋值操作符通常定义为成员。与赋值操作符不同的是,不一定飞的这样做,如果定义为非成员,编译器不会报告错误。
3、改变对象状态或者与给定类型紧密联系的其他一些操作符,入自增,自减和解引用,通常定义为类成员
4、对称的操作符,for example:算数操作符、相等操作符、关系操作符和位操作符,最好定义为非成员函数。
5、IO操作符必须定义为非成员函数,我们不能将该操作符定义为类的成员函数,否则,左操作数将只能是该类类型的对象。
第4.1: 类的访问标识。
(1)private成员:
1.*可以由该类的成员函数访问内以
this指针或
直接访问或
在成员函数内以“类实例.成员”的方式访问。
2.可以由其友元函数访问。
/*类ATPString的私有成员ptr_string, 在类的成员函数内, 可以用"实例.私有成员"的方式访问*/
ATPString::ATPString(const ATPString& ref) { if(this == &ref) return; string_length = ref.length(); ptr_string = (char*)malloc(string_length+1); strcpy(ptr_string,ref.ptr_string); // notice for(unsigned int i=0; i<string_length; i++) { temp = ref.get_char_at(i); memcpy(ptr_string+i,&temp,1); } } |
(2)protected成员:
1.可以被“类自身”或其派生类的成员函数
直接访问,
2.可以被“类自身”或其派生类的成员函数通过this指针访问,而派生类不能访问base.value,
3.可以被类的友元访问。
派生类只能通过派生类对象访问基类的protected成员, 派生类对其基类对象的protected成员没有访问权限.*下面的代码说明了, Base的protected成员value_protected在派生类中的访问权限:
void Derive::test(Base& b, Derive& d) { cout << b.value_protected << std::endl; //error! cout << this->value_protected << std::endl; cout << d.value_protected << std::endl; cout << value_protected << std::endl; } |
(3)public成员:
1. 可以被该类中的函数、2.子类的函数、3.其友元函数访问,也可以由 4.该类的对象访问。
第4.2: 类的继承后成员属性变化。
private 属性不能够被继承。
使用 public继承, 基类中的protected和public属性不发生改变;
使用 protected继承,基类的protected和public属性在派生类中变为protected;
使用
private继承, 基类的protected和public属性在派生类中变为private;
第5.1:(虚的)析构函数
在说明前,先看一个例子,Base,Derive(基类,派生类):
Derive* pd = new Derive();
pd->~Derive(); //区别delete pd;
//解释,当pd->~DeriveItem()时,会先执行~DeriveItem(),再执行~Base(),保证基类和派生类的资源都得到释放;
第二个例子,假若Base,Derive的析构都不是virtual的,
Base* pd = new Derive();//基类指针指向派生类对象
delete pd;
这时候只会执行Base::~Base(),而没有执行~Derive;
所以,如果一个类要当做基类, 那么它的析构必须写为virtual的;
如果函数中定义的"栈变量", 比如BaseClass b, 在函数退出时自动执行~BaseClass();
在说明前,先看一个例子,Base,Derive(基类,派生类):
Derive* pd = new Derive();
pd->~Derive(); //区别delete pd;
//解释,当pd->~DeriveItem()时,会先执行~DeriveItem(),再执行~Base(),保证基类和派生类的资源都得到释放;
第二个例子,假若Base,Derive的析构都不是virtual的,
Base* pd = new Derive();//基类指针指向派生类对象
delete pd;
这时候只会执行Base::~Base(),而没有执行~Derive;
所以,如果一个类要当做基类, 那么它的析构必须写为virtual的;
如果函数中定义的"栈变量", 比如BaseClass b, 在函数退出时自动执行~BaseClass();
#new delete, free malloc#
malloc和new都申请空间,但是new是强类型的分配,会调用对象的构造函数初始化对象,而malloc仅分配内存空间但是不初始化。
new 自适应类型,malloc需要强制转换.
new按类型进行分配,malloc需要指定内存大小.
对于对象来说free的确释放了对象的内存,但是不调用对象的析构函数。delete不仅释放对象的内存,并且调用对象的析构函数.
所以在对象中用free删除new创建的对象,内存就有可能泄露.
#在delete内部仍调用了free .
malloc和new都申请空间,但是new是强类型的分配,会调用对象的构造函数初始化对象,而malloc仅分配内存空间但是不初始化。
new 自适应类型,malloc需要强制转换.
new按类型进行分配,malloc需要指定内存大小.
对于对象来说free的确释放了对象的内存,但是不调用对象的析构函数。delete不仅释放对象的内存,并且调用对象的析构函数.
所以在对象中用free删除new创建的对象,内存就有可能泄露.
#在delete内部仍调用了free .
待续 : http://hi.baidu.com/striveforit/item/637e14dc4b269e2b38f6f78f
第5.2:成员的初始化顺序:
顺序是按照Thing类声明中的顺序,而不是构造函数中赋值的顺序;
第6.1:函数的重载(区分重写):
"重写" override:也叫覆盖,指virtual函数在继承中的定义改写,返回值和形参在过程中保持一致;virtual关键字仅能在函数声明中出现,(纯)虚函数的定义句中不能带有virtual关键字;
"重载" overload:是同域内的同名函数可由形参表区分(返回值可以不同,且仅返回值不能确定重载);
"隐藏"是非虚函数在继承中的定义改写,返回值和形参可以不一致.产生覆盖的函数仍是"静态绑定",基类指针不能调用子类函数;
const修饰对函数重载的影响:
仅当函数的形参是指针或引用时, const关键字才对重载有影响.
第7:类设计的健壮性建议:
1.如果复制构造函数的代码很困难写出,为了避免用户调用"编译器合成的复制构造函数",可以把此函数写为private;
2.(纯)虚函数写为private;
3.构造函数/析构函数写为private,这样可以...
(1)防止此类被继承,被当做基类;
(2)构造函数声明为virtual,编译出错,构造函数执行时还未形成vtable;
(3)析构函数声明为virtual,这是一般性做法, 如果一个类要作为基类, 那么它的析构函数必须为virtual, why ?
4.把需要检查值的成员写为private: 设计一个"分数"类,分子分母int num和int deom,如果把deom成员写public,作容易出现用户直接写a.deom=0这样的代码,
#解决方案: deom设为private,新建一个set_deom(int)的成员函数,在set_deom中进行deom==0的检查.
#解决方案: deom设为private,新建一个set_deom(int)的成员函数,在set_deom中进行deom==0的检查.
5.函数形参使用const,例如void func(const int*)或void func(const int&),防止函数内意外修改实参;或者类的某成员函数声明为const,"void func() const;",则此函数不能修改类的数据成员,否则编译器报错;
下面的代码可通过:
void func(const int*);
int *ptr = NULL;
func(ptr); //正常调用
下面的代码不可通过:
void func(char*);
const char* prt = "hello"; //字符串常量
func(prt); //调用出错