您的位置:首页 > 编程语言 > C语言/C++

C++学习心得--类

2011-03-27 22:35 239 查看
1、类的数据成员的初始化可以采用初始化表或函数体内赋值两种方式,这两种方式的效率不完全相同。非内部数据类型的成员对象应当采用第一种方式初始化,以获取更高的效率。内部数据类型的数据成员而言,两种初始化方式的效率几乎没有区别,但后者的程序版式似乎更清晰些。不能在类声明中初始化const 数据成员,类的const 常量只能在初始化表里被初始化。

2、要主动编写拷贝构造函数和赋值函数,如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。倘若类中含有指针变量,那么这两个缺省的函数就隐含了错误。以类String 的两个对象a,b 为例,假设a.m_data 的内容为“hello”,b.m_data 的内容为“world”。现将a 赋给b,缺省赋值函数的“位拷贝”意味着执行b.m_data = a.m_data。这将造成三个错误:一是b.m_data 原有的内存没被释放,造成内存泄露;二是b.m_data 和a.m_data 指向同一块内存,a 或b 任何一方变动都会影响另一方;三是在对象被析构时,m_data 被释放了两次。如果我们实在不想编写拷贝构造函数和赋值函数,又不允许别人使用编译器生成的缺省函数,只需将拷贝构造函数和赋值函数声明为私有函数,不用编写代码。

3、示例:
//String的普通构造函数
String::String(const char *str)
{
if(str==NULL)
{
m_data = new char[1];
*m_data = ‘/0’;
}
else
{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data, str);
}
}

// String 的析构函数
String::~String(void)
{
delete [] m_data;
// 由于m_data 是内部数据类型,也可以写成 delete m_data;
}

// 拷贝构造函数
String::String(const String &other)
{
// 允许操作other 的私有成员m_data
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
}

// 赋值函数
String & String::operate =(const String &other)
{
// (1) 检查自赋值
if(this == &other)
return *this;
// (2) 释放原有的内存资源
delete [] m_data;
// (3)分配新的内存资源,并复制内容
//函数strlen 返回的是有效字符串长度,不包含结束符'/0'。函数strcpy 则连'/0'一//起复制
int length = strlen(other.m_data);
m_data = new char[length+1];
strcpy(m_data, other.m_data);
// (4)返回本对象的引用
return *this;
}

4、使用const 提高函数的健壮性
a、const 只能修饰输入参数。如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const 修饰,否则该参数将失去输出功能。
b、如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。
c、如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。
d、对于非内部数据类型的参数而言,象void Func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很 容易,加const修饰即可,因此函数最终成为void Func(const A &a)。
e、如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
f、任何不会修改数据成员的函数都应该声明为const 类型。const 关键字只能放在函数声明的尾部。

5、一些有益的建议
a、当心那些视觉上不易分辨的操作符发生书写错误。我们经常会把“==”误写成“=”,象“||”、“&&”、“<=”、“>=”这类符号也很容易发生“丢1”失误。然而编译器却不一定能自动指出这类错误。
b、变量(指针、数组)被创建之后应当及时把它们初始化,以防止把未被初始化的变量当成右值使用。
c、当心变量的初值、缺省值错误,或者精度不够。
d、当心数据类型转换发生错误。尽量使用显式的数据类型转换(让人们知道发生了什么事),避免让编译器轻悄悄地进行隐式的数据类型转换。
e、当心变量发生上溢或下溢,数组的下标越界。
f、当心忘记编写错误处理程序,当心错误处理程序本身有误。
g、当心文件I/O 有错误。
h、避免编写技巧性很高代码。
i、不要设计面面俱到、非常灵活的数据结构。
j、如果原有的代码质量比较好,尽量复用它。但是不要修补很差劲的代码,应当重新编写。
k、尽量使用标准库函数,不要“发明”已经存在的库函数。

6、关于继承和组合
若在逻辑上B 是A 的“一种”(a kind of),并且A 的所有功能和属性对B 而言都有意义,则允许B 继承A 的功能和属性。
若在逻辑上A 是B 的“一部分”(a part of),则不允许B 从A 派生,而是要用A 和其它东西组合出B。

7、指针p 被free 以后其地址仍然不变(非NULL),只是该地址对应的内存是垃圾,p 成了“野指针”。如果此时不把p 设置为NULL,会让人误以为p 是个合法的指针。如果用语句if (p != NULL)进行防错处理,但此时if 语句起不到防错作用,因为即便p 不是NULL 指针,它也不指向合法的内存块。
//有点问题,见下面程序,输出结果正确无误
#include <iostream.h>
#include <malloc.h>
#include <string.h>
int main()
{
char *p = (char *) malloc(100);
strcpy(p, "hello");
cout<<p;
free(p); // p 所指的内存被释放,但是p 所指的地址仍然不变
if(p != NULL) // 没有起到防错作用
{
strcpy(p, "world"); // 出错
}
cout<<p;
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: