多次释放的指针

如果指向地址不是NULL的指针被释放多次,那么编译阶段不会报错,但是程序运行时会有问题。
可以多次free指向NULL的指针。

[edemon@CentOS workspace]$ g++ class.cpp
[edemon@CentOS workspace]$ ./a.out
*** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0840d008 ***
======= Backtrace: =========
/lib/libc.so.6[0x280c91]
/lib/libc.so.6[0x2836b2]
./a.out[0x804864d]
/lib/libc.so.6(__libc_start_main+0xe6)[0x226d26]
./a.out[0x8048431]
======= Memory map: ========
001cc000-001e9000 r-xp 00000000 fd:00 924257     /lib/libgcc_s-4.4.7-20120601.so.1
001e9000-001ea000 rw-p 0001d000 fd:00 924257     /lib/libgcc_s-4.4.7-20120601.so.1
001ee000-0020c000 r-xp 00000000 fd:00 924233     /lib/ld-2.12.so
0020c000-0020d000 r--p 0001e000 fd:00 924233     /lib/ld-2.12.so
0020d000-0020e000 rw-p 0001f000 fd:00 924233     /lib/ld-2.12.so
00210000-003a1000 r-xp 00000000 fd:00 924234     /lib/libc-2.12.so
003a1000-003a2000 ---p 00191000 fd:00 924234     /lib/libc-2.12.so
003a2000-003a4000 r--p 00191000 fd:00 924234     /lib/libc-2.12.so
003a4000-003a5000 rw-p 00193000 fd:00 924234     /lib/libc-2.12.so
003a5000-003a8000 rw-p 00000000 00:00 0
003ef000-00417000 r-xp 00000000 fd:00 924256     /lib/libm-2.12.so
00417000-00418000 r--p 00027000 fd:00 924256     /lib/libm-2.12.so
00418000-00419000 rw-p 00028000 fd:00 924256     /lib/libm-2.12.so
00c24000-00c25000 r-xp 00000000 00:00 0          [vdso]
05a3f000-05b2e000 r-xp 00000000 fd:00 282337     /usr/lib/libstdc++.so.6
05b2e000-05b32000 r--p 000ef000 fd:00 282337     /usr/lib/libstdc++.so.6
05b32000-05b33000 rw-p 000f3000 fd:00 282337     /usr/lib/libstdc++.so.6
05b33000-05b3a000 rw-p 00000000 00:00 0
08048000-08049000 r-xp 00000000 fd:00 395364     /home/edemon/workspace/a.out
08049000-0804a000 rw-p 00000000 fd:00 395364     /home/edemon/workspace/a.out
0840d000-0842e000 rw-p 00000000 00:00 0          [heap]
b7600000-b7621000 rw-p 00000000 00:00 0
b7621000-b7700000 ---p 00000000 00:00 0
b7709000-b770c000 rw-p 00000000 00:00 0
b7721000-b7723000 rw-p 00000000 00:00 0
bfd6e000-bfd83000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

new内置了sizeof,类型转化,类型安全检查的功能。

纯虚函数

纯虚函数的声明类似于:
functype funcname() = 0;
带有纯虚函数的类是不能被实例化的。在定义的时候就不用写virtual前缀了。
/home/edemon/workspace/pureVirtual

自己写一个String引发的思考

使用const 需要using namespace std;
运算符重载operator不要写成operate。
类里面的指针不会被默认初始化为NULL!

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
using namespace std;

class String{
    public:   
    String(const char *str = NULL); // 普通构造函数
    String(const String& other);    // 拷贝构造函数
    ~String(void); // 析构函数
    String & operator = (const String& other); // 赋值函数
    char *m_data;  // 用于保存字符串
};

String::String(const char *str){
    m_data = (char *)malloc(strlen(str)+1);
    if(m_data == NULL) {
        fprintf(stderr,"malloc error: %d",__LINE__);
        exit(1);
    }
    strcpy(m_data,str);
}

String & String::operator = (const String& other){
    m_data = (char *)malloc(strlen(other.m_data)+1);
    if(m_data == NULL) {
        fprintf(stderr,"malloc error: %d",__LINE__);
        exit(1);
    }
    strcpy(this->m_data,other.m_data);
    return *this;
}

String::~String(void){
    if(m_data != NULL){
        free(m_data);
        m_data = NULL;
    }
}

String::String(const String &other){
    m_data = (char *)malloc(strlen(other.m_data)+1);
    if(m_data == NULL) {
        fprintf(stderr,"malloc error: %d",__LINE__);
        exit(1);
    }
    strcpy(this->m_data,other.m_data);
}

int main(){
    String str1("hello");
    String str2(str1);
    String str3 = str2;
    printf("%s\n%s\n%s\n",str1.m_data,str2.m_data,str3.m_data);
    return 0;
}

不少难以察觉的程序错误是由于变量没有被正确初始化或清除造成的,而初始化和清除工作很容易被人遗忘。

关于初始化表

如果类存在继承关系,派生类必须在其初始化表里调用基类的构造函数。
类的const常量只能在初始化表里被初始化,因为它不能在函数体内用赋值的方式来初始化。

构造和析构

次序

构造从类层次的最根处开始,在每一层中,首先调用基类的构造函数,然后调用成员对象的构造函数。析构则严格按照与构造相反的次序执行。

成员对象初始化的次序完全不受它们在初始化表中次序的影响,只由成员对象在类中声明的次序决定。这是因为类的声明是唯一的,而类的构造函数可以有多个,因此会有多个不同次序的初始化表。如果成员对象按照初始化表的次序进行构造,这将导致析构函数无法得到唯一的逆序。

如果不主动编写拷贝构造函数和赋值函数,编译器将以“位拷贝”的方式自动生成缺省的函数。(操作地址)

  • 基类的构造函数、析构函数、赋值函数都不能被派生类继承。
  • 在编写派生类的赋值函数时,注意不要忘记对基类的数据成员重新赋值。

    虚的析构函数

    基类与派生类的析构函数应该为虚(即加 virtual 关键字)。

  • #include <iostream.h>
    class Base
    {
        public:
        virtual ~Base() { cout<< "~Base" << endl ; }
    };
    class Derived : public Base
    {
        public:
        virtual ~Derived() { cout<< "~Derived" << endl ; }
    };
    void main(void)
    {
        Base * pB = new Derived; // upcast
        delete pB;
    }
  • 输出结果为:

  • ~Derived
    ~Base
  • 如果析构函数不为虚,那么输出结果为
    ~Base

    const

    const只能修饰驶入变量。
    对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)。
    对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)。
    如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
    const成员函数:
    任何不会修改数据成员的函数都应该声明成const成员函数。

  • class dog{
        public:
        dog();
        ~dog();
        char *getname() const;
        private:
        char *name;
    };

    分类: career

    发表评论

    电子邮件地址不会被公开。 必填项已用*标注