C++ Primer 笔记五

第九章 内存模型和名称空间

9.1单独编译

#ifndef COORDIN_H_ #define COORDIN_H_ …… #endif

9.2存储持续性、作用域和链接性

自动存储持续性:在函数定义中声明的变量的存储持续性为自动的。函数结束后自动销毁

静态存储持续性:在函数定义外定义的变量和使用关键字static定义的变量的存储持续性都是静态的。

动态存储持续性:使用new操作符分配的内存一直存在,知道使用delete操作符将其释放或者程序结束为止,这种内存的存储持续性为动态,有时又称为自由存储。

作用域和链接:

自动变量的作用域是局部的,静态变量的作用域是局部还是全局取决于它是如何被定义的。

自动存储持续性:

在默认情况下,在函数中声明的函数参数和变量的持续性为自动,作用域是局部的,没有链接性。

函数内的局部变量可以暂时隐藏函数外的同名变量。当程序离开函数时,函数外的变量又从新可见。

自动变量和堆栈:堆栈是后进先出(LIFO),程序使用两个指针来跟踪堆栈,一个指针指向栈底(堆栈开始的位置),一个指针指向堆顶(下一个可用内存单元)。

寄存器变量registerC++也支持使用register关键字来声明局部变量,他的持续性为自动,作用域是局部,没有链接性。关键字register表示变量不存储在堆栈里,而是存储在cpu的寄存器中,以方便快速访问。register int ireg = 9;如果变量存储在寄存器中则没有内存地址,因此不能对它进行取地址操作,因此&ireg是错误的。

静态持续变量:

在默认情况下如果没有显式地初始化静态变量,编译器就将它设置为0静态数组和结构将每个元素或成员的所有位都设置为0

 

int global = 1000;

double warming = 0.4;

static int one_file = 50;

int main()

{ ….. return0; }

void update(double dt)

{

extern double warming;

warming++;

}

void local()

{ static double au=90

double warming=8;

double a =::warming;

}

 

global的持续性为静态,作用域是整个文件,链接性为外部,即另外的文件也能访问它

one_file的持续性为静态,作用域是整个文件,链接性为内部,即只有这个文件能访问它,其他文件不行。

1,静态持续性,外部链接性

extern duble warming; 表示使用externwarming变量进行重新声明,该关键字的意思是,使用以前被外部定义的名称来使用该变量。C++提供了作用域解析操作符(::),当放在变量的前面时,表示使用该变量的全局版本。因此在local()中,::warming表示的是warming的全局版本,并不是局部变量。(其他文件如果需要使用warming都需要使用extern引用声明该变量)

2,静态持续性,内部链接性

static int one_file = 50;表示one_file只能在这个文件中使用,其他文件不能访问它。

3,静态存储持续性,无链接性

在函数内部定义的使用static修饰的变量,无链接性。

说明符和限定符

存储说明符:auto\register\static\extern\mutable(如果结构中某个成员被mutable修饰,则及时结构为const,该成员依然可以被修改,不受const限制)在同一个声明中不能使用多个说明符。

vc限定符:const(限定变量不能修改)\volatile(变量随时可能被改,每次取值都须访问变量,不能读缓存)

const:在默认情况下全局变量的链接性是外部的,但是const全局变量的链接性是内部的,在C++看来,全局const定义就像使用了static说明符一样。

const的内部链接性还意味着,每个文件都有自己的一组常量,而不是所有的文件共享一组常量,每个定义都是其所属文件私有的,这就是能够将常量定义放在头文件中(可以被任何文件引用)的原因

如果出于某种原因,程序员想要将const常量的链接性变成外部,可以使用extern来覆盖默认的内部链接性

extern const int aa= 100;这种情况下,必须在所有使用该常量的文件中用extern关键字来引用,并且初始化。

函数和链接性

C++不允许在一个函数内定义另一个函数,因此所有的函数的存储持续性都是静态的,在整个程序执行期间都存在。默认为链接性是外部的。但是有static修饰过,链接性即为内部的。必须同时在原型和函数定义中使用static关键字。

语言链接性(language linkage

编译器执行名称矫正或名称修饰,为重载函数生成不同的符号名称。叫做语言链接性。

9.3布局new操作符

通常new负责在堆(heap)中找到一个能够满足要求的内存块。new操作符还有一个变型,称为布局new操作符,它让程序员能够指定要使用的位置。布局new操作符不能与delete配套使用,如果使用布局new操作符建立类对象,需要则必须显式调用析构函数,这将在12章中讲述。头文件为:#include<new>

 

#include<new>

struct chaff

{

char dross[20];

int slag;

};

char buffer1[50];

char buffer2[500];

 

 

 

 

int main()

{

chaff *p1,*p2;

int *p3,*p4;*p5,*p6

p1=new chaff;

p3=new int[20];

p2=new (buffer1) chaff;

p4=new (buffer2) int;

p5=new(buffer2) int;

p6=new(buffer(2)+sizeof(int));

……..

}

 

p2=new (buffer1) chaff; p4=new (buffer2) int;程序分别从buffer1buffer2中分配空间给了两个指针。

p5会覆盖存储p4值的内存块,但是p6不会,会接着p4的地址重新分配内存.

9.4名称空间

声明区域:可以在其中进行声明的区域,其声明有效的区域。比如全局变量则声明区域为声明所在的文件,局部变量其声明区域为声明所在的代码块。

潜在作用域:声明点开始,到其声明区域的结尾。潜在作用域比声明区域小。变量必须定以后才能使用

作用域:变量对程序而言可见的区域是变量的作用域,变量并不是在其潜在作用域都可见的,可能会被嵌套声明区域中声明的同名变量所隐藏。

新的名称空间特性:通过定义一种新的声明区域来创建命名的名称空间,一个名称空间中的名称不会与另一个名称空间里的名称冲突,同时允许程序的其他部分使用该名称空间中声明的东西。

 

namespace Jack

{

double pail;

void fetch();

int pal;

struct Well{……};

}

namespace Jill

{

double bucket(double n){……};

void fetch();

int pal;

struct Hill{……};

}

 

名称空间是全局的,可以位于一个另名称空间中,但不能在代码块中。在默认情况下,名称空间中声明的名称的链接性是外部的。用户可定义名称空间,还有系统的全局名称空间。任何名称空间中的名称都不会与其他名称空间中的名称发生冲突。

通过作用解析操作符(::)来访问名称空间的名称:Jack::pal = 12;

using声明和using编译指令

我们不希望每次都使用作用解析操作符来访问名称空间里的名称,因此使用using声明和using编译指令来简化。

using声明由被限定的名称和它前面的关键字using组成:using Jill::fetch;

using编译指令使所有的名称可用:using namespace Jill;

using声明和using编译指令的比较

使用using编译指令导入一个名称空间中所有的名称,相当于大量使用作用域解析操作符。声明区域中依然可以定义同名的局部名称,局部名称将隐藏名称空间名。但是依然可以使用作用域解析操作符来调用。

使用using声明时,就像声明了相应的名称一样。不能在作用空间里定义同名变量。

嵌套名称空间namespace one{ namespace two{ int n;}}  

使用方法:using namespace one::two;和using one::two::n;

未命名的名称空间namespace {int ice; int band;}

该名称空间中声明的名称的潜在作用域为:从声明点到声明区域末尾。由于没有名称,所以不能被其他程序调用。

C++标准不赞成在名称空间和全局作用域中使用关键字static….

 

不赞成:

static int counts; //全局静态变量

int others; //全局变量

int main(){…..}

推荐:

namespace {int counts;}

int others;

int main(){……}

 

名称空间及其前途:

指导原则:

使用在已命名的名称空间中的变量,而不是使用外部全局变量

使用在已命名的名称空间中的变量,而不是使用静态全局变量

导入名称时,首选使用作用解析操作符或using声明的方法

对于using声明,首选将其作用域设置为局部而不是全局

第十章 对象和类

重要的面向对象编程(opp)特性:抽象、封装和数据隐藏、多态、继承、代码的可重用性

10.1过程性编程和面向对象编程

面向对象编程,更注重从用户的角度考虑对象,描述对象所需的数据,描述用户与数据交互所需的操作,如何实现接口和数据存储,以及如何展示数据。

10.2抽象和类

C++里面用户定义类型指的是实现抽象接口的类设计。

类型:决定了数据对象需要的内存数量;决定了如何解释内存中的位(longfloat内存中占用的位数相同,但是它们转换成数值的方法不同);决定了使用数据对象执行的操作或方法(用户自定义或系统自带)。

类:是一种将抽象转化为用户数据类型的C++工具,它将数据表示和操控数据的方法组合成一个简洁的包。

 

#include <iostream>

#include <cstring>

class Stock //类的声明

{

private:

char company[30];

int shares;

double share_val;

double total_val;

void set_tot(){total_val=shares*share_val; }

public:

Stock(const char *co,int mum=0,double price=0.0);

void acquire(const char *co,int n,double pr);

void buy(int num,double price);

void sell(int num,double price);

void update(double price);

void show();

};

 

 

//下面是类的定义

Stock::Stock(const char *co,int num,double price)

{

std::strncyp(company,co,29);

company[29]=’\0’;

if(n<0) shares=0;

else shares=n;

shre_val=pr;

}

void Stock::acquire(const char *co,int n,double pr)

{……}

void Stock::buy(int num,double price)

{……}

void Stock::sell(int num,double price)

{…..}

void Stock::update(double price)

{…..}

void Stock::show()

{…..}

 

类规范有两个部分组成:

类声明:以数据成员的方式描述数据部分,以成员函数(又称为方法)的形式描述公有接口。

类方法定义:定义如何实现类成员函数

接口:接口是一个共享框架,供两个系统(如两个类之间,或者计算机和打印机之间……)交互时使用。

类设计尽可能地将公有接口和实现细节分开。公有接口表示设计的抽象组件。将实现细节放在一起并将它们与抽象分开被称为封装。封装的另一个例子是:将类函数的定义和类声明放在不同的文件中。

控制对成员的访问:publicprivate。可以不必在类声明中使用关键字private,这是类对象的默认访问控制

类和结构:结构的默认访问类型是public,类的为private

实现类成员函数:

成员函数的定义和常规函数定义十分相似,也有返回类型和参数,它们还有两个特殊的特性:

定义成员函数时,使用作用域解析操作符(::)来表示函数所属的类 void Stock::update(double price)

类方法可以访问类的private组件

内联方法:其定义位于类声明中的函数都将自动成为内联函数,如果愿意也可以将类声明外定义的函数定义为内联函数,需要加上inline限定符。

内联函数的特殊规则要求在每个使用它们的文件中都对其进行定义。确保内联定义对多文件程序的所有文件都可用的、最简便的方法是:将内联定义放在定义类的头文件中。根据改写规则,在类声明中定义方法等同于用原型特换方法定义,然后在类声明的后面将定义改写为内联函数。因此定义位于类声明中的函数,和定义紧跟类声明后,并使用inline限定符的函数是等价的。

对象的方法使用

所创建的每个新对象都有自己的存储空间,用于存储其内部变量和类成员;但是同一个类的所有对象共享同一组类方法。例如,假设katejeo都是Stock的对象,则kate.shares\joe.shares占用的内存块不同。但是kate.show()\joe.show()都调用同一个方法。也就是说它们要执行同一个代码块,只是将这些代码用于不同的数据。

10.3类的构造函数和析构函数

 

struct thing

{ char *pn;

int m; };

thing abc={“worry”,22}; //valid,结构的成员默认为public,可以直接赋值

stock hot={“shock”,200,50}; //invalid,类成员默认为private,不能直接访问

 

 

类构造函数,专门用于构造新对象,来初始化他们的数据成员。构造函数的名称与类名相同,构造函数的原型和函数头虽然没有返回值,但是没有被声明为void类型。实际上,构造函数没有声明类型。

不能将类成员名称用作构造函数的参数:

Stock::Stock(const char *company,int shares,double share_val) {…} //NO!

因为构造函数的参数表示的不是类成员,而是赋给类成员的值。因此不能相同,否则将出现shares=shares。。。

使用构造函数:

Stock food =Stock(“world cabbage”,250,1.25);

Stock food(“world cabbage”,250,1.25);

Stock *pfood = new Stock(“world cabbage”,250,1.25);

可以使用对象来调用方法,food.show();但是不能使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。

默认构造函数:

类声明为Stock food;时,如果类没有提供任何构造函数,则C++将自动提供默认构造函数Stock::Stock(){  },但是当且仅当没有定义任何构造函数时,编译器才会提供默认构造函数。为类声明了构造函数后,程序员必须提供构造函数的定义(默认构造函数Stock::Stock(){}或者非默认构造函数Stock::Stock(const char *co,int num,double price){})。如果提供了非默认构造函数,但没有提默认构造函数,则声明为Stock food;将会出错。如果要创建对象,而不显式地初始化,则必须定义一个不接受任何参数的默认构造函数,定义默认构造函数可以有两种方式,一种是给已有的构造函数的所有参数提供默认值:Stock(const char *co=”No name”,int mum=0,double price=0.0);,一个是,通过函数重载来定义另一个构造函数,一个没有任何参数的构造函数:Stock(); Stock::Stock(){….}

析构函数

用构造函数创建对象后,程序负责跟踪对象,直到其过期为止,对象过期时,程序自动调用析构函数,析构函数完成清理工作。和构造函数一样,析构函数的名称也很特殊,在类名前面加一个(~)符号。析构函数也没有返回类型和返回值。与构造函数不同的是,析构函数没有参数。~Stock(); Stock::~Stock(){ };

何时调用析构函数:这由便以其决定的。通常不应在代码中显式地调用析构函数。什么时候类对象过期,则程序会自动调用析构函数。程序必须有一个析构函数,如果程序员没有提供,则编译器将隐式地声明一个默认析构函数。

const成员函数

const Stock land=Stock(); land.show();

但是编译器会报错,因为show函数不能保证调用对象不被修改,但是land不能被修改。我们以前将函数参数声明为const引用或指向const的指针来解决这种问题,但是show()方法没有任何参数。想法,他所使用的对象都是由方法调用隐式地提供的。保证函数不会修改调用对象的另一种方法是:把show()声明称:void show() const;

这种方式声明和定义的类函数被称为const成员函数,在函数声明和定义中,在函数名后添加const。就像应尽可能将const引用和指针用作函数形参一样,只要类方法不修改调用对象,就应将其声明为const

构造函数和析构函数小结:

如果构造函数只有一个参数,则将对象初始化为一个与参数的类型相同的值时,该构造函数将被调用.

,构造函数原型为:Bozo(int age);则可以使用下面的任何一种形式来初始化对象:

Bozo one = Bozo(44); Bozo two(44); Bozo three =32;

接受一个参数的构造函数允许使用赋值句法来将对象初始化为一个值。

每个类只能有一个析构函数,而一个类可能有多个重载构造函数。如果构造函数式用了new,则必须提供使用delete的析构函数

10.4 this指针

this指针指向用来调用成员函数的对象,所有的类方法都将this指针设置为调用它的对象的地址。每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法要引用整个调用对象,则可以使用表达式*this,因为this是对象的地址,而不是对象本身,即*this(将解除引用操作符*用于指针,将得到指针指向的值)。00000000000

10.5对象数组

声明对象数组的方法与声明标准数组的方法相同。可以分别用构造函数来初始化数组元素。

Stock mystuff[4];如果不进行初始化则系统会调用默认构造函数进行初始化。

Stock stock[10]={Stock(“Smart”,12,120),Stock(),Stock(“Momo”,13,10)};前三个按照规定的初始化,其他用默认构造函数

注意:初始化对象数组的方案是,首先使用默认构造函数创建数组元素,然后花括号中的构造函数将创建临时对象,然后将临时对象的内容复制到相应的元素中。因此,要创建类对象数组,则这个类必须有默认构造函数。

10.7类作用域

类定义的名称(如数据成员名和类成员函数名)的作用域都是整个类。因此作用域为整个类的名称,在类中是已知的,在类外是不可知的。因此,可以在不同的类中间使用同名的成员,不会冲突。外部也不能直接访问类成员,公有成员都不行。调用公有成员必须通过对象。

作用域为整个类的常量

类中不能声明const类型常量,因为const常量必须在声明时初始化,但声明类只是描述了对象的形式,并没有真正创建对象,因此没有用于存储的空间

在类中声明常量有两种方法:

 

第一种在类中声明一个枚举:

class Stock { 

private:

enum{Len=30};

char company[len];

}

第二种使用关键字static(保存在栈中)

class Stock { 

private:

static const int Len=30;

char company[len];

}

 

10.9总结

通常,将类声明分成两部分组成,这两部分通常保存在不同的文件中。类声明(包括由函数原型表示的方法)应放在头文件中。定义成员函数的源代码放在方法文件中。这样便于将接口描述与实现细节分开。

类是用户定义类型,对象是类的一个实例。

每个对象都存储自己的数据,而共享类方法。

如果方法需要显式地调用对象本身,则可以使用this指针,由于this指针表示的是对象的地址,*this表示对象别名

 

第十一章 使用类

11.1操作符重载

一种形式的C++多态operator op(argument-list) 

如:Time operator + (const Time &t) const;

Time Time::operator + (const Time &t) const

{

Time sum;

sum.minutes = minutes + t.minutes

sum.hours = hours + t.hours;

return sum;

}

为区分++操作符的前缀版本和后缀版本,C++operator++最为前缀版本,将operator++( Time)作为后缀版本;其中的参数永远也不会被用到,所以不必指定其名称。

Time Time::operator ++{…} Time Time::operator ++(Time){…}

重载限制

1,重载的操作符不必是成员函数,但必须至少有一个操作数是用户定义类型,防止用户为标准类型重载操作符

2,使用操作符时不能违反操作符的原来的句法规则,不能改变操作符的优先级

3,不能定义新的操作符(C++原本不存在的操作符)

4,有些操作符不能被重载,有些操作符只能通过成员函数进行重载,大多数操作符都可以通过成员函数和非成员函数进行重载

友元简介:友元函数,友元类,友元成员函数

为类重载二元操作符时,如乘法操作符将一个Time和一个double类型结合在一起。

A=B*2.45为:A=B.operate(2.45); 然而A=2.45* B则不能转换。

有一种解决方式,非成员函数。原型为:Time operate*(double m,const Time &t);则能使A=2.45* B也能运算

但是常规非成员函数不能直接访问类的私有数据,但,有一类特殊的非成员函数可以访问私有成员,叫做友元函数

函数声明,放在类声明里面:friend Time operate*(double m,const Time &t);

函数定义:由于他不是成员函数,所有不要使用Time::限定符,另外也不要在定义时使用关键字friend

friend Time operate*(double m,const Time &t)

{….友元函数的访问权限与成员函数相同,都可以访问类的私有成员}

友元函数是否有悖于oop:只有类声明可以决定哪一个函数是友元,因此类声明仍然控制了那些函数可以访问私有数据。简而言之,类方法和友元只是表达类接口的两种不同机制。

如果要为类重载操作符,并将非类的一项作为其第一个操作数,则可以使用友元函数来反转操作数。

重载操作符,作为成员函数还是非成员函数。

对于很多操作符来说,可以选择使用成员函数或非成员函数来实现操作符重载。一般来说,非成员函数是友元函数,这样才能访问类的私有成员。

如:Time operate+ (const Time & t) const;和friend Time oprate+(const Time &t1, const Time &T2) const;

将转化为:T3=T1.operate(T2) T3=operate+(T1,T2)

在定义操作符的时候只能选择一种,因为这两种格式都是同一个表达式匹配,同时定义这两种格式将被视为二义性,导致编译错误。

11.6类的自动转换和强制类型转换(未看)

第十二章 类的动态分配内存

静态类成员特点:无论创建了多少对象,程序都只创建一个静态类变量副本。类的所有对象共享同一个静态成员static

在类声明的时候不能对静态数据成员进行初始化,静态数据成员在类声明中声明,在包含类方法的文件中(类定义的时候)初始化。初始化时使用作用域操作符来指出静态成员所属的类,但是如果静态成员是整型或枚举型const,则可以在类声明中初始化。

在构造函数中使用new来分配内存是,必须在相应析构函数中使用delete来释放内存,如果使用new[]来分配内存,则应使用delete[]来释放内存。如:int a = new int(); delete a;   和  char* a=new char[]; delete[] a;

如果函数用传值调用的方式调用类对象,则程序会生成一个临时变量,将类对象赋值给临时变量,并把临时变量传递给函数。

StringBad sailors=sports;等效于StringBad sailors=StringBad(sports);构造函数原型为:StringBad(const StringBad &);因为当构造函数只接受一个参数时,可以以赋值的方式初始化对象。

当你使用一个对象来初始化另一个对象时,编译器将自动生成上述构造函数(称为赋值构造函数,因为它创建对象的一个副本)。

隐式成员函数

C++自动提供了下面这些成员函数:默认构造函数、复制构造函数、赋值操作符、默认析构函数、地址操作符。

隐式地址操作符返回调用对象的地址。

1.默认构造函数:程序自动生成的构造函数,用户显式定义的没有参数的显式默认构造函数。用户定义的所有参数都有默认值的默认构造函数。以上三种都可以称为默认构造函数,并且程序只能有一个默认构造函数,如果后两种都在程序中定义,则会出现二义性,因为这样Class_name a;将不知道调用哪个构造函数。

2.复制构造函数:复制构造函数用于将一个对象复制到新创建的对象中,它用于初始化过程中,而不是常规的赋值过程中。原型如下:Class_name(const Class_name &);它接受一个指向类对象的常量引用作为参数。

新建一个对象并将它初始化为同类现有对象时,复制构造函数将会被调用。以下四种声明都将调用复制构造函数

1.Class_name a(b); 2.Class_name a=b; 3. Class_name a=Class_name(b); 4.Class_name *pa=new Class_name(b);

其中中间的两种可能会使用复制构造函数直接创建对象,也可能使用复制构造函数生成一个临时对象。然后将临时对象的内容赋给a,这取决于具体的实现,第4个声明使用b初始化一个匿名对象,并将新对象的地址赋给pa指针。

每当程序生成了对象副本时,编译器都将使用复制构造函数。具体来说,当函数按值传递对象或函数返回对象时,都将使用复制构造函数。按值传递意味着创建元是变量的一个副本,编译器生成临时对象是,也将使用复制构造函数。

功能、问题、解决方法:默认复制构造函数逐个复制非静态成员(成员复制也叫浅复制),复制的是成员的值,即如果类中包含了使用new初始化的指针成员,应当定义一个复制构造函数,以赋值指向的数据,而不是指针,这被称为深度复制,复制的另一种形式(成员复制或浅复制)只是复制指针值。浅复制仅浅浅的复制指针信息,而不会深入挖掘来复制指针引用的结构。

3.复制操作符:Class_name & Class_name::operator=(const Class_name &);

C允许结构复制,而C++允许类对象复制,这个通过自动为类重载赋值操作符实现的。将已有的对象赋值给另一个对象时,将使用重载的赋值操作符:StringBad a(“I’m bad string.”); Stringbad b; b=a;

StringBad metoo = knot;这个是新创建的对象metoo,初始化为knot的值,因此使用复制构造函数,不过也可能会分两步来处理,使用复制构造函数创建一个临时变量,然后使用复制操作符将临时对象的值复制到新对象中。

与复制构造函数相似,复制操作符的隐式实现也对成员进行逐个复制,如果成员本身就是类对象,则程序将使用为这个类定义的复制操作符来复制该成员,但静态数据成员不受影响。

StringBad & StringBad::operator=(const StringBad &st)

{

if (this=&st) return *this;

delete [] str;

len=st.len;

str = new char[len+1];

std::strcpy(str,st.str);

return *this;

}

静态成员函数,不能通过对象来调用静态成员函数,甚至不能使用this指针。如果静态成员函数是在共有部分声明的,可以使用类名和作用域解析操作符来调用它。static int HowMany(){return num_str;} int count=String::HowMany();

由于静态成员函数只与特定的对象相关联,因此只能使用静态数据成员,如静态方法HowMany只能访问静态成员num_str,不能访问str len

在构造函数中使用new时应注意的事项

1.如果在构造函数中使用new来初始化对象的指针成员,则应在析构函数中使用delete

2.newdelete必须相互兼容。new对应于delete[]

3.如果有多个构造函数,则必须以相同的方式使用new。要么都带中括号,要么都不带,因为只有一个析构函数,因此所有的构造函数都必须与它兼容。不过,可以在一个构造函数中使用new来初始化指针,而在另外一个构造函数中将指针初始化为空(NULL0),这是因为delete(无论是带中括号还是不带中括号)都可以用于空指针。

应定义一个复制构造函数,通过深度复制将一个对象初始化为另一个对象。

具体来说,复制构造函数应分配足够的空间来存储复制的数据,并复制数据,而不仅仅是数据的地址,另外,还应更新所有受影响的静态类成员。

应当定义一个赋值操作符,通过深度复制将一个对象赋值给另一个对象。

当成员函数或独立的函数返回对象时,有几种返回方式可供选择,返回指向对象的引用,指向对象的const引用,指向对象的const对象。

如果方法或函数要返回局部对象,则应返回对象,而不是指向对象的引用。这种情况下将使用复制构造函数来生成返回的对象。如果方法或函数要返回一个没有公有复制构造函数的类(如ostream)的对象,它必须返回一个指向这种对象的引用。

返回const对象,则不能通过对象改变类的成员。

指针和对象:

使用常规表示法来声明指向对象的指针:String *gl;

可以将指针初始化为指向已有的对象(不创建新对象):String *first=&saystring[0];

可以使用new来初始化指针,这将创建一个新的对象:String *favorite =new String(saystring[choice]);

对类使用new将调用相应的类构造函数来初始化新创建的对象。

String *a = new String; //默认构造函数

String *b = new String(“bbb”); //String(const char *);

String *c = new String (saystring[0]); //String(const String &);

可以使用->操作符通过指针访问类方法:a->length();

可以对对象指针应用解除引用操作符(*)来获得对象:String d = *a;

再谈new布局操作符

程序员必须负责管理布局new操作符从中使用的缓冲区内存单元,防止两个变量的内存单元重叠。

如果使用布局new操作符来为对象分配内存,必须确保其析构函数被调用,布局new操作符不能与delete配合使用。例如,指针p3没有收到new操作符返回的地址,因此delete p3将导致运行阶段错误。因为new/delete系统知道已分配的字节块,但是对于new操作符对该内存块做了何种处理一无所知。解决方案是:显式地为使用布局new操作符的对象调用析构函数。显式调用析构函数时,必须指定要销毁的对象。对于使用布局new操作符创建的对象,应以与创建的顺序相反的顺序进行删除,原因在于,晚创建的对象可能依赖于早创建的对象,另外,仅当所有对象都被销毁后,才能释放用于存储这些对象的缓冲区。

 

#include<new>

class JustTesting

{

private:

string words;

int number;

public:

JustTesting (Const string &s = “JustTesting”,int n=0){ words =s; number =n;cout<<words<<” constructed;\n”;}

~ JustTesting(){cout<<words<<” destoryed;\n”;}

show()const{ cout<<words<<” , ”<<number<<endl;}

};

 

int main()

char* buffer = new char[512];

JustTesting *p1,*p2;

p1=new (buffer) JustTesting;

p2=new JustTesting(“Heap1”,20); 

p3= new (buffer+sizeof(JustTesting)) JustTesting(“BetterIdea”,6);

p4 = new JustTesting(“Heap2”,22);

…..

delete p2;delete p4;

p3->~ JustTesting();p1->~ JustTesting();

delete[] buffer;

}


转换函数(详细见11.6类的自动转换和强制转换,并未细看):

要将单个值(type_name)转换为类类型(c_name),需要创建类构造函数:c_name(type_name value);

要将类类型(c_name)转换为其他类型(type_name),需要创建类构造函数:operator type_name();虽然该函数没有声明返回类型,但是应返回所需类型的值。

在转换函数时要小心,可以在声明构造函数式使用关键字explicit,防止它被用于隐式转换。

在类中可以嵌套结构或者类声明。这样它们的作用域是内部的,只是在类中使用。

对于class中的const变量,不能再构造函数中给它赋值


Class Test

{

private:

const int size;

int & count;

…..

}

Test::Test(int a,int b):size(a),count(b)

{

size = a; //invalible

count = b; //invalible

}


由于调用构造函数时,对象将在大括号中的代码执行之前被创建。但是const 和引用对象必须在创建对象时被初始化,这样就会发生错误。因此,C++提供一种特殊的句法来完成上述工作,它叫做成员初始化列表。成员初始化列表由都好分隔的初始化列表组成完成(前面带冒号)。只有构造函数能够使用这种初始化列表句法。const类成员,被声明为引用的类成员必须使用这个句法,其他类成员也可以使用。

 

12.3总结

 

如果对象包含成员指针,同时他指向的内存是由new分配的。则释放用于保存对象的内存并不会自动释放对象成员指针指向的内存,因此在类构造函数中使用new类来分配内存时,应在类析构函数中使用delete来释放分配的内存。这样,当对象过期时,将自动释放其指针成员指向的内存。

 

posted @ 2014-08-27 19:06  tt_tt--->  阅读(192)  评论(0编辑  收藏  举报