林锐C/C++高质量编程指南之二
只是记了一部分我认为比较难理解的,或常用的,部分掌握的就没有写。
第七章内存管理
【规则 7-2-1】用 malloc 或 new 申请内存之后,应该立即检查指针值是否为 NULL。 防止使用指针值为 NULL 的内存。
【规则 7-2-2】不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右 值使用。
【规则 7-2-3】避免数组或指针的下标越界,特别要当心发生“多 1”或者“少 1” 操作。
【规则 7-2-4】动态内存的申请与释放必须配对,防止内存泄漏。
【规则 7-2-5】用 free 或 delete 释放了内存之后,立即将指针设置为 NULL,防止产 生“野指针”
以字符串为例比较指针与数组的特性。
示例 7-3-1 中,字符数组 a 的容量是 6 个字符,其内容为 hello 。a 的内容可以改变, 如 a[0]= ‘X’。
指针 p 指向常量字符串“world”(位于静态存储区,内容为 world ),常 量字符串的内容是不可以被修改的。
从语法上看,编译器并不觉得语句 p[0]= ‘X’有什么 不妥,但是该语句企图修改常量字符串的内容而导致运行错误
char a[] = "hello"; a[0] = 'X'; cout << a << endl; char *p = "world"; // 注意 p 指向常量字符串 p[0] = 'X'; // 编译器不能发现该错误 cout << p << endl;
内容复制与比较
计算内存容量:
char a[] = "hello world"; char *p = a; cout<< sizeof(a) << endl; // 12 字节 cout<< sizeof(p) << endl; // 4 字节
void Func(char a[100])
{
cout<< sizeof(a) << endl; // 4 字节而不是 100 字节
}
7.4指针参数是如何传递内存的
看下面的几个例子, 好多笔试题遇见过这几个例子
例一:
void GetMemory(char *p) { p = (char *)malloc(100); } void Test(void) { char *str = NULL; GetMemory(str); strcpy(str, "hello world"); printf(str); } 请问运行 Test 函数会有什么样的结果?
答:程序崩溃。
因为 GetMemory 并不能传递动态内存,
Test 函数中的 str 一直都是 NULL。
strcpy(str, "hello world");将使程序崩溃。
例二:
void GetMemory(char **p, int num) { *p = (char *)malloc(num); } void Test(void) { char *str = NULL; GetMemory(&str, 100); strcpy(str, "hello"); printf(str); } 请问运行 Test 函数会有什么样的结果? 答: (1)能够输出 hello (2)内存泄漏 (没有加free函数)
例三:
char *GetMemory(void) { char p[] = "hello world"; return p; } void Test(void) { char *str = NULL; str = GetMemory(); printf(str); } 请问运行 Test 函数会有什么样的结果? 答:可能是乱码。 因为 GetMemory 返回的是指向“栈内存” 的指针, 该指针的地址不是 NULL,但其原 现的内容已经被清除,新内容不可知
例四:
void Test(void) { char *str = (char *) malloc(100); strcpy(str, “hello”); free(str); if (str != NULL) { strcpy(str, “world”); printf(str); } } 请问运行 Test 函数会有什么样的结果? 答:篡改动态内存区的内容,后果难以预料,非常危险。 因为 free(str);之后,str 成为野指针, if(str != NULL)语句不起作用。
7.6:动态分配的内存会自动释放吗?
void GetMemory(char *p) { p = (char *)malloc(100); // 动态内存会自动释放吗 }
我们发现指针有一些“似是而非”的特征:
(1)指针消亡了,并不表示它所指的内存会被自动释放。
(2)内存被释放了,并不表示指针会消亡或者成了 NULL 指针。
7.7野指针
“野指针”不是 NULL 指针,是指向“垃圾”内存的指针
造成野指针出现的原因:
(1)指针变量没有被初始化。例如:char *p; 这个就是野指针,应该修改为char *p = NULL;
(2)指针 p 被 free 或者 delete 之后,没有置为 NULL
7.11new和delete的使用要点:
int *p1 = (int *)malloc(sizeof(int) * length); int *p2 = new int[length]; delete p2; 如果用 new 创建对象数组,那么只能使用对象的无参数构造函数。 Obj *objects = new Obj[100]; // 创建 100 个动态对象 在用 delete 释放对象数组时,留意不要丢了符号‘[]’。例如 delete []objects; // 正确的用法 delete objects; // 错误的用法 后者相当于 delete objects[0],漏掉了另外 99 个对象。
第八章:C++的高级特性
如果 C++程序要调用已经被编译后的 C 函数
假设某个 C 函数的声明如下:
void foo(int x, int y);
该函数被 C 编译器编译后在库中的名字为_foo,而 C++编译器则会产生像_foo_int_int 之类的名字用来支持函数重载和类型安全连接。
由于编译后的名字不同,C++程序不能 直接调用 C 函数。
C++提供了一个 C 连接交换指定符号 extern“C”来解决这个问题。
extern “C” { void foo(int x, int y); … // 其它函数 }
或者:
extern “C”
{
#include “myheader.h”
… // 其它 C 头文件
}
这就告诉 C++编译译器,函数 foo 是个 C 连接,应该到库中找名字_foo 而不是找 _foo_int_int。
C++编译器开发商已经对 C 标准库的头文件作了 extern“C”处理,所以我 们可以用#include 直接引用这些头文件
注意并不是两个函数的名字相同就能构成重载。全局函数和类的成员函数同名不算 重载,因为函数的作用域不同
调用时要加上作用域运算符
第九章:类的构造函数,析构函数,赋值函数
class String { public: String(const char *str = NULL); // 普通构造函数 String(const String &other); // 拷贝构造函数 ~ String(void); // 析构函数 String &operator =(const String &other); // 赋值函数 private: char *m_data; // 用于保存字符串 };
构造函数的列表初始化:
如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
拷贝构造函数和赋值函数,当涉及到默认的含有内存操作时,要注意。
。以类 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 被释放了两次。
string类的成员函数实现:
// 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)分配新的内存资源,并复制内容 int length = strlen(other.m_data); m_data = new char[length + 1]; strcpy(m_data, other.m_data); // (4)返回本对象的引用 return *this; }