对类中的成员 使用动态内存分配
复制构造函数(有隐式 和 显式 两种方法)
类 在程序运行时分配内存 ,可 通过在 类的构造函数中使用 new运算符 —— 这样就可以在程序运行时 进行内存分配(例如,string 类 就通过这种方式,实现了 在运行时进行内存分配。string 类 提供的接口 是非常有好的——这其中 就涉及了大量的编程技术)
在类中使用 new运算符时,就必须定义 析构函数——用来 释放内存。(在类中使用new运算符时,除了要定义析构函数,有时 还需要 重载赋值运算符——用来保证程序的正常运行。)
类 中的静态成员变量 不在类声明中进行初始化——而是 在类声明代码块之外 使用单独的语句进行初始化(静态成员的初始化语句 不应在类声明文件中——而是 在 类成员函数定义 的那个文件中,对 类的 静态成员进行初始化。这是因为 类声明 代码块 是在 头文件中——头文件 可能会被多个文件包含,如果 对 静态成员进行初始化的语句 也在头文件中,就有可能 出现 对同一个静态成员进行多次初始化,这样会引发错误。但是也有一种情况 是可以在类中 对静态成员进行初始化——就是当 类的 静态成员 为整型或 const枚举型 时 可以 在类声明中直接对 类的静态成员进行初始化。) ——并且初始化语句 不需要 使用 static关键字,例如 int StringBad::num_strings=0;。(这是因为 ,类中 的静态成员 是单独存储的——不是单个对象的组成部分)
strlen() 函数 返回字符串(不包括末尾的空字符)的长度
类的构造函数 使用new分配的内存,是属于一块独立的内存(堆——和 类的对象 所在的内存是分开的)。
删除类的对象 能够释放 对象本身 所占的内存,但并不能自动释放 对象 中的 成员(指针) 指向的内存(也就是 构造函数中new出来的内存),因此 必须在析构函数中 使用 delete语句 来释放 之前在构造函数中 用new分配得到的内存。(如果 动态分配内存的时候 使用的是 new [] 这种语法,那么释放该内存的时候 应该使用 delete [] 这种语法来释放内存。例如 分配内存:str=new char[len+1]; 释放内存:delete [] str;)
当创建一个 新对象 并且 有其他对象对其进行初始化的时候(这时,初始化该对象 所使用的构造 函数 不是 自己定义的构造函数 也不是 系统默认的构造函数,而是 由编译器 自动生成的一个构造函数——叫做复制构造函数。这个复制构造函数的函数原型为:StringBad(const StringBad &);)
类中 C++默认提供的 成员函数 有:默认构造函——在没有定义构造函数的情况下提供(默认构造函数的定义为:Klunk::Klunk(){}),默认析构函数——在没有定义析构函数的情况下提供,复制构造函数——在没有定义复制构造函数的情况下提供(一般,当 使用 一个对象 对另一个对象初始化的时候会 调用该函数;当程序 生成对象副本的时候,也会调用该函数——比如 当函数 按值传递对象时 或者 当函数 返回对象时 都会 调用 复制构造函数;还有就是 当编译器生成临时对象时 也会调用 复制构造函数),赋值运算符——在没有对赋值运算符进行重载的情况下提供(一般是 将一个对象 赋值 给 另一个对象的时候 才会自自动冲对赋值运算符进行重载——重载赋值运算符 的 函数原型 为 StringBad & StringBad::operator=(const StringBad &); 重载实现的这个赋值运算符 返回的是 一个 指向 类对象 的引用),地址运算符——在没有对地址运算符进行重载的情况下提供(一般是 返回对象的地址的时候 会自动生成)。(除此之外 C++11 还提供了 另外两个 特殊的成员函数:移动构造函数 和 移动赋值运算函数)
由于 按值 传递对象 会调用 复制构造函数,所以 一般应该选用 按引用传递 对象——这样不会 调用复制构造函数,同时减少程序的开销
对于 new 分配的内存 ,如果 只复制 指向这块内存的指针——这被称为浅复制;如果 开辟一块新的内存,并在这块新的内存中,生成 new分配的内存中的 数据 的副本,然后 生成一个 指向 副本内存的指针 ——这叫做深复制
默认的复制构造函数——主要功能是 复制 非静态成员(这里复制的是 非静态成员的值(如果又 指向 new分配的内存的 指针成员,那么对该成员采用的是 浅复制)。并且 复制构造函数 不会对 静态成员进行复制,因为静态车成员 属于整个类 而不是只属于单个对象)
StirngBad knot; knot=headline1;(这两条语句,虽然用到了 赋值运算符,但实际上是 创建了一个对象并 使用另一个对象对其进行初始化,这会调用复制构造函数——但不一定会 重载 复制运算符。并且 自动重载的复制运算符 和 自动生成的 复制构造函数 功能类似,都是 对 类对象的 非静态成员进行 浅拷贝)
如果将一个指针的值置0,那么就相当于将这个指针设置为空指针。(0有两个含义,一个是 数值0, 另一个 是可以表示 空指针——空指针还有其其他形式:如使用(void *)0 来表示空指针、使用 C语言 中的 宏 NULL 来表示空指针、C++11中引入了 关键字 nullptr 来表示空指针——str= nullptr,这样 str就被设置为 空指针。例如 str=0,那么str 就是变成了空指针。空指针 也可以使用 delete[] 这种语法 来对其进行内存释放——本质上不会做任何操作。)
delete 只能用于 使用 new函数返回的指针 和 空指针 (用于释放 指针所指向的内存空间)
在进行函数重载(或运算符重载的时候),带cosnt关键字 和 不带const 关键字 的函数 算是两种函数,编译器是可以区分的——因此 进行函数重载的时候,可以重载两个具有相同参数列表的函数——但是必须是 一个是添加 const关键字 一个是不添加const关键字
静态成员函数 属于整个类 不属于单个对象,因此静态成员函数 只能通过 类名::函数名()的方式 进行调用,不能通过 对象名或者 this 指针 来进行调用。(并且静态成员函数 只能访问静态成员变量,不能访问 非静态成员变量)
空指针有多重表示方法:0、NULL、C++11中 使用 nullptr 来表示空指针
空字符 用 ‘\0’ 来表示。
如果一个函数 最终 返回的是 对象,那么该函数返回对象的时候会调用 复制构造函数;如果一个函数 最终 返回的是对象的引用,那么返回结果的时候 就不会调用 复制构造函数。
ostream 类 没有公有 的复制构造函数(所以,一般 当此类的对象作为函数的返回结果的时候,函数返回 此类对象的引用 ——这样就不需要调用复制构造函数)。(也就是说 如果 函数 要放回一个 类的对象 ,但是该类 没有 公有的 复制构造函数,那么该函数只能返回 一个 指向 该对象的 引用)
使用new 来 创建(可能会便随着初始化)的对象,当时用 delete 对象 这种语法 删除对象的时候,会调用对象的析构函数 。其他类别的对象,是在这些对象销毁的时候,才。会调用 这些对象的析构函数(比如 如果对象是 静态变量,该变量 是在程序结束的时候自动销毁——也就是说在程序结束的时候,才会调用这些变量的析构函数。对于非静态变量,那么它们是在执行完 它们所在的代码块的时候 会自动销毁——也就是说 当他们 所在的代码块执行完毕的时候,会调用这些变量的析构函数)
通过指针访问 类中的成员函数 的 语法是:int s=point -> length();
对于 使用 定位new 运算符创建的对象,使用 delete释放内存的时候,并不会自动调用对象的析构函数——因此 对于 使用 定位new运算符创建的对象,必须显示的调用 该对象的析构函数(语法为:p->~JustTesting();)。(因此 对于 定位new运算符创建的对象,的正确的删除和释放内存的方式是:首先按照 和创建顺序想法的顺序 依次的调用对象的析构函数——来销毁所有对象,例如,p2->~JustTesting();p1->~JustTesting();;当所有对象都被销毁之后,然后使用 delete语句来释放内存——比如,delete [] buffer;)
一个类 只能有一个析构函数 但是可以有多个 构造函数——因此 如果涉及到 使用 new来分配内存,这些构造函数 都要 和 这一个 析构函数对应(也就是说, 如果析构函数中 用的 是 delete[]语句来对内存进行释放,那么所有的构造函数中都必须使用 new[] 这种语句来分配内存;如果 析构函数中 用的 是 delete 语句来释放内存 ,那么所有的构造函数中必须 使用 new 这种 语句来分配内存)
C++ 的类 中 可以嵌套 其他类或结构——这些 在类1中 嵌套的 类2或结构 的作用域是 这个类1,也就是说,即使 类1外面 有和 类1内 一模一样定义的 类2或结构,也不会发生冲突。(有一些 老的 编译器 不支持 这种 类中 再嵌套 类或结构)
当调用类的构造函数 创建对象时,对象 将在 构造函数 中的代码执行之前 进行创建——也就是说 会首先 给对象中的成员变量 分配内存,之后 在执行 构造函数代码的时候,才将 具体的值 赋给每个成员(也就是 将具体的值 放到相应的内存中)。(也就是说 如果对象中含有 const成员,那么该成员 在构造函数的代码执行前 就已经被创建了——因此不能 在构造函数中 对其更改。针对 对象中的const成员,C++提供了一种 语法(成员初始化类表)来 在 const成员创建的时候进行初始化。成员初始化类表的语法为:Queue::Queue(int qs):qsize(qs) { ; ;}。 这种语法不仅可用于初始化 const成员 ,也可以用于初始化任何 其他 对象成员,例如 Queue::Queue(int qs):qsize(qs),front(NULL),rear(NULL),items(0) {; ; ;} 。 只有构造函数才可以使用 成员初始化列表 这种语法,并且 对于 对象 中的 const 成员 和 引用类型的成员 必须通过 这种语法进行初始化——因为 const成员 和 引用类型的成员 都只能在创建的时候进行初始化;其他成员则 可以不用这种语法进行初始化;并且 当使用 成员初始化列表 的时候,列表中 对象成员 的初始化顺序 和 类声明中的顺序一致——而不是 按照 成员初始化列表中 书写的顺序)
如果 类 的构造函数中 使用了new来分配内存 ,那么 该类 中一般要 对 复制构造函数进行定义 和 赋值元素符进行重载——以实现 深复制。(同样 实现 队列的 类 也需要实现 深复制。并且如果将 上面提到的 两个 成员函数 设置为 private——实现的效果 就是 不允许 进行 类的 负值,也就是说 不允许 使用 该 类的对象 初始化 其他的 该类对象 ,也不允许 将 该类的对象 赋值给 其他的 该类对象 )
可以将两个类 合并到一个文件中——也就是说 ,将两个类声明文件 合并成一个 文件 ;将 两个类 的 成员函数定义文件 合并为 一个 文件。