- 动态内存和类
- 静态类成员
- 特殊成员函数
- string类的改进
- 构造函数中的new
- 返回对象
- 指向对象的指针
- 成员初始化列表(member initializer list)
动态内存和类 静态类成员
- 静态类成员有一个特点:无论创建了多少对象,程序都只创建一个静态类变量副本。
也就是说,类的所有对象共享同一个静态成员,就像家中的电话可供全体家庭成员共享一样。
- 不能在类声明中初始化静态成员变量,这是因为声明描述了如何分配内存,但并不分配内存。
对于静态类成员,可以在类声明之外使用单独的语句来进行初始化,这是因为静态类成员是单独存储的,而不是对象的组成部分。
- 对于不能在类声明中初始化静态数据成员的一种类外情况是,静态数据成员为
const
整数类型或枚举型。 - 总之,静态数据成员在类声明中声明,在包含类方法的文件中初始化。
初始化时使用作用域运算符来指出静态成员所属的类。
但如果静态成员是
const
整数类型或枚举型,则可以在类声明中初始化。
C++自动提供了下面这些成员函数:
-
默认构造函数,如果没有定义构造函数;
如果没有提供任何构造函数,C++将创建默认构造函数,也就是说,编译器将提供一个不接受任何参数,也不执行任何 *** 作的构造函数(默认的默认构造函数),这是因为创建对象时总是会调用构造函数;
带参数的构造函数也可以是默认构造函数,只要所有参数都有默认值。
-
默认析构函数,如果没有定义;
如果没有提供任何析构函数,C++将创建默认析构函数。
默认析构函数不执行任何 *** 作,这里不展开讨论。
-
复制构造函数,如果没有定义;
-
复制构造函数用于将一个对象复制到新创建的对象中,也就是说,它用于初始化过程中。
具体地说就是,新建一个对象并将其初始化为同类现有对象时,复制构造函数都将被调用。
-
还有每当程序生成了对象副本时,编译器都将使用复制构造函数。
具体地说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。
记住,按值传递意味着创建原始变量的一个副本,编译器生成临时对象时,也将使用复制构造函数。
-
默认的复制构造函数逐个复制非静态成员(成员复制也称为浅复制),复制的是成员的值。
-
如果类中包含静态数据成员,即其值将在新对象被创建时发生变化,则应该提供一个显式复制构造函数来处理计数等问题。
原因在于隐式复制构造函数是按值进行复制的。
另外,如果遇到指针,按值复制只能复制一个指针,也就是一个地址,这就导致不同对象共用一个指针,释放的时候会导致同一块内存被释放两次,从而导致程序异常终止。
-
解决这种问题的方法是进行深度复制(deep copy),也就是说,复制构造函数应当复制字符串并将副本的地址赋给新的对象成员,而不仅仅是复制字符串地址。
总之,如果类中包含了使用
new
初始化的指针成员,应当定义一个复制构造函数,以复制指向的数据,而不是指针,这被称为深度复制。复制的另一种形式(成员复制或浅复制)只是复制指针值。
-
-
赋值运算符,如果没有定义;
将已有的对象赋给另一个对象时,将使用重载的赋值运算符。
针对对象使用赋值运算符时,会分两步来进行处理:使用复制构造函数创建一个临时对象,然后通过赋值将临时对象的值复制到新对象中。
和复制构造函数相同,也会有成员复制等问题;解决办法就是提供赋值运算符(进行深度复制)定义。
其实现与复制构造函数相似,但也有一些差别:
- 由于目标对象可能引用了以前分配的数据,所以函数应使用
delete[]
来释放这些数据; - 函数应当避免将对象赋给自身;否则,给对象重新赋值前,释放内存 *** 作可能删除对象的内容;
- 函数返回一个指向调用对象的引用。
- 由于目标对象可能引用了以前分配的数据,所以函数应使用
-
地址运算符,如果没有定义。
隐式地址运算符返回调用对象的地址(即
this
指针的值),符合我们的初衷,不需要展开讨论。
编译器将生成上述最后三个函数的定义——如果程序使用对象的方式要求这样做。
例如,如果您将一个对象赋给另一个对象,编译器将提供赋值运算符的定义。
C++11提供了另外两个特殊成员函数:移动构造函数(move constructor)和移动赋值运算符(move assignment operator),这两个将在后面进行详细讨论。
这一节主要是举了几个例子,增加了几个string
类的功能。
具体地说,将添加以下方法:
int length() const {return len;}
friend bool operator<(const String &st, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st, const String &st2);
friend bool operator>>(istream & is, String &st);
char & operator[](int i);
const char & operator[](int i) const;
static int HowMany();
–string.h
–
#ifndef STRING_H_
#define STRING_H_
#include
using std::ostream;
using std::istream;
class String
{
private:
char * str; // pointer to string
int len; // length of string
static int num_strings; // number of objects
static const int CINLIM = 80; // cin input limit
public:
// constructors and other methods
String(const char * s); // constructor
String(); // default constructor
String(const String &); // copy constructor
~String(); // destructor
int length() const {return len;}
// overloaded operator methods
String & operator=(const String &);
String & operator=(const char *);
char & operator[](int i);
const char & operator[](int i) const;
// overloaded operator friends
/*
将比较函数作为友元,有助于将String对象与常规的C字符串进行比较。
**这个地方需要一个具体的例子再说明一下成员函数和友元的区别!**
*/
friend bool operator<(const String &st, const String &st2);
friend bool operator>(const String &st1, const String &st2);
friend bool operator==(const String &st, const String &st2);
friend istream & operator<<(ostream & os, const String & st);
friend istream & operator>>(istream & is, String & st);
// static function
static int HowMany();
};
#endif
–string.cpp
–
#include
#include "string.h"
using std::cin;
using std::cout;
//initializing static class member
int String::num_strings = 0; // 初始化静态变量
//static method
/*
1. 首先,不能通过对象调用静态成员函数;实际上,静态成员函数甚至不能使用this指针。
如果静态成员函数是在共有部分声明的,则可以使用类名和作用域解析运算符来调用它。
调用方式: int count = String::HowMany();
2. 其次,由于静态成员函数不与特定的对象相关联,因此只能使用静态数据成员。
*/
int String::HowMany()
{
return num_strings;
}
//class methods
String::String(const char * s) // construct String from C string
{
len = std::strlen(s); // set size
str = new char[len + 1]; // allot storage
std::strcpy(str, s); // initialize pointer
num_strings++; // set object count
}
String::String() // default constructor
{
len = 4;
str = new char[1];
str[0] = ';'// default string ++
num_strings;}
String
::String(const& String ) st++
{
num_strings;// handle static member update =
len . st;len// same length =
str new char [+len 1 ];// allot space ::
stdstrcpy(,str. st)str;// copy string to new location }
String
::~String()// necessary destructor --
{
;num_strings// required delete
[ ]; str// required }
//overloaded operator methods
// assign a String to a String
&
String :: Stringoperator=(const& String ) stif
{
( this== & )streturn
* this;delete
[ ]; str=
len . st;len=
str new char [+len 1 ];::
stdstrcpy(,str. st)str;return
* this;}
//assign a C string to a string
&
String :: Stringoperator=(charcosnt * ) sdelete
{
[ ]; str=
len :: stdstrlen()s;=
str new char [+len 1 ];::
stdstrcpy(,str) s;return
* this;}
//read-write char access for non-const string
char
& :: Stringoperator[](int) ireturn
{
[ str]i;}
//read-only char access for const String
const
char & :: Stringoperator[](int) iconst return
{
[ str]i;}
//overloaded operator friends
bool
/*
要实现字符串比较函数,最简单的方法是使用标准的strcmp()函数,
如果依照字母顺序,第一个参数位于第二个参数之前,则该函数返回一个负值;
如果两个字符串相同,则返回0;
如果第一个参数位于第二个参数之后,则返回一个正值。
*/
operator <(const& String ,st1const & String )st2return
{
( ::stdstrcmp(.st1,str. st2)str< 0 );}
bool
operator (>const& String ,st1const & String )st2return
{
< st2 ; st1}
bool
operator ==(const& String ,st1const & String )st2return
{
( ::stdstrcmp(.st2,str. st1)str== 0 );}
//simple String output
&
ostream operator <<(&ostream , osconst & String ) st<<
{
os . st;strreturn
; os}
//quick and dirty String input
&
istream operator (>>&istream , is& String ) stchar
{
[ temp::String]CINLIM;.
isget(,temp:: String)CINLIM;if
()is=
st ; tempwhile
(&&is . isget()!= '\n' )continue
;return
; is}
new
构造函数中的new
-
在构造函数中使用
delete
来分配内存时,必须在相应的析构函数中使用new[]
来释放内存。如果使用
delete[]
(包括中括号)来分配内存,则应使用new
(包括中括号)来释放内存。 -
如果有多个构造函数,则必须以相同的方式使用
new
,要么都带中括号,要么都不带。因为只有一个析构函数,所有的构造函数都必须与它兼容。
然而,可以在一个构造函数中使用
nullptr
初始化指针,而在另一个构造函数中将指针初始化为空(0或C++11中的delete
),这是因为delete
(无论是带中括号还是不带中括号)可以用于空指针。(也就是说,如果析构函数通过对指针类成员使用
new
来释放内存,则每个构造函数都应当使用NULL
来初始化指针,或将它设置为空指针。)
0
、nullptr
还是0
:以前,空指针可以用NULL
或NULL
(在很多头文件中,0
是一个被定义为NULL
的符号常量)来表示。C程序员通常使用
0
而不是'
,以指出这是一个指针,就像使用0
'0
而不是NULL
来表示空字符,以指出这是一个字符一样。然而,C++传统上更喜欢用简单的
nullptr
,而不是等价的String。但正如前面指出的,C++11提供了关键字::,这是一种更好选择。
-
应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。
通常,这种构造函数与下面类似。
String(const&) String ++ st; { num_strings// handle static member update= . len ; st// same lengthlen= new str char [ +1len ] ;// allot space:: strcpy std(,.str) st;str// copy string to new location} & ::
具体地说,复制构造函数应该分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址。
另外,还应该更新所有受影响的静态类成员。
-
应当定义一个赋值运算符,通过深度复制讲一个对象复制给另一个对象。
通常,该类方法与下面类似:
String operator String=(const&) String if st( { this ==& ) // object assigned to itselfstreturn * this ;// all donedelete [ ] ;// free old string str= . len ; st=lennew str char [ +1len ] ;// get space for new string:: strcpy std(,.str) st;str// copy the stringreturn * this ;// return reference to invoking object} // use pointers to keep track of shortest, first strings // sayings是一个数组,元素为字符串(谚语);shortest指向最短的,first指向按字母顺序排在最前面的
-
如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用,因为在被调用函数执行完毕时,局部对象将调用其析构函数。
因此,当控制权回到调用函数时,引用指向的对象将不再存在。
在这种情况下,将使用复制构造函数来生成返回的对象。
-
如果方法或函数要返回一个没有公有复制构造函数的类(如ostream类)的对象,它必须返回一个指向这种对象的引用。
-
最后,有些方法和函数(如重载的赋值运算符)可以返回对象,也可以返回指向对象的引用,在这种情况下,应首选引用,因为其效率更高。
比如,如果函数返回(通过调用对象的方法或将对象作为参数)传递给它的对象,可以通过返回引用来提高效率。
-
总之,返回对象将调用复制构造函数,而返回引用不会。
所以返回引用所做的工作更少,效率更高。
其次,引用指向的对象应该在调用函数执行时存在。
另外,如果引用被声明为const引用,则返回类型必须为const,这样才匹配。
-
先举个例子感受一下用指针来跟踪对象(可以用于在对象数组中挑选符合条件的对象):
* = String & shortest [ 0sayings];// initialize to first object* = String & first [ 0sayings];for( int=1 i ; <; i ++ total) iif( { []sayings.ilength()<length ( shortest->))=& shortest [ ]sayings;iif( []sayings<i* ) =first& first [ ]sayings;i}
new
delete
-
析构函数将被调用的情形:
- 如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
- 如果对象是静态变量(外部、静态、静态外部或来自名称空间),则在程序结束时将调用对象的析构函数。
- 如果对象是
String * glamour;
创建的,则仅当您显式使用String * first = &sayings[0];
删除对象时,其析构函数才会被调用。
- 如果对象是动态变量,则当执行完定义该对象的程序块时,将调用该对象的析构函数。
-
使用对象指针时,需要注意几点:
- 使用常规表示法来声明指向对象的指针:
new
- 可以将指针初始化为指向已有的对象:
String * favorite = new String(sayings[choice]);
- 可以使用
new
来初始化指针,这将创建一个新的对象:
(*)
- 对类使用
Classy
将调用相应的类构造函数来初始化新创建的对象 - 可以使用
->
运算符通过指针访问类方法 - 可以对对象指针应用解除引用运算符
mem1
来获取对象
- 使用常规表示法来声明指向对象的指针:
如果mem2
是一个类,而mem3
, Classy, ::都是这个类的数据成员,则类构造函数可以使用如下的语法来初始化数据成员:
Classy(int,int n) : mmem1( ),nmem2( 0),mem3( *+n2m)//···}
{
mem1
n
上述代码将mem2
初始化为0
,将mem3
初始化为n*m+2
,将
const
。从概念上说,这些初始化工作是在对象创建时完成的,此时还未执行括号中的任何代码。
请注意以下几点:
- 对于
const
类成员,必须使用这种语法(必须用这种格式来初始化非静态数据成员); - 对于被声明为引用的类成员,也必须使用这种语法,这是因为引用和数据类似,只能在被创建时进行初始化。
const
数据成员被初始化的顺序与它们出现在类声明中的顺序相同,与初始化器中的排列顺序无关。
警告:不能将成员初始化列表语法用于构造函数之外的其他类方法。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)