• 林锐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。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;
    }

    课后题及答案

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

  • 相关阅读:
    由于服务主机:DCOM服务进程占用过多CPU,导致系统卡死
    MySQL优化
    input type="file"文件上传到后台读取
    mysql 创建事件
    Quartz.Net实现的定时执行任务调度
    js 编码详解
    C# DateTime.Now 详解
    C# 读写text 详细讲解
    百度地图API详细介绍
    layui table 详细讲解
  • 原文地址:https://www.cnblogs.com/xiaokang01/p/12716736.html
Copyright © 2020-2023  润新知