林锐C/C++高质量编程指南之二

林锐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\0。a 的内容可以改变, 如 a[0]= ‘X’。

指针 p 指向常量字符串“world”(位于静态存储区,内容为 world\0),常 量字符串的内容是不可以被修改的。

从语法上看,编译器并不觉得语句 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;
}

 

课后题及答案

https://www.cnblogs.com/xiaokang01/p/12718592.html

posted @ 2020-04-17 10:53  Lucky&  阅读(647)  评论(0编辑  收藏  举报
//返回顶部开始
//返回顶部结束