int numbers [20];
int * p;
下面的赋值为合法的:
p = numbers;
这里指针p 和numbers 是等价的,它们有相同的属性,唯一的不同是我们可以给指针p赋其它的数值,而numbers 总是指向被定义的20个整数组中的第一个。所以,p只是一个普通的指针变量,而与之不同,numbers 是一个指针常量(constant pointer),数组名的确是一个指针常量。
内存分配的方法:
动态内存分配 (Dynamic memory)
到目前为止,我们的程序中我们只用了声明变量、数组和其他对象(objects)所必需的内存空间,这些内存空间的大小都在程序执行之前就已经确定了。但如果我们需要内存大小为一个变量,其数值只有在程序运行时 (runtime)才能确定,例如有些情况下我们需要根据用户输入来决定必需的内存空间,那么我们该怎么办呢?
答案是动态内存分配(dynamicmemory),为此C++ 集成了操作符new 和delete。
操作符 new 和 delete 是C++执行指令。本节后面将会介绍这些操作符在C中的等价命令。
操作符new 和new[ ]
操作符new的存在是为了要求动态内存。new 后面跟一个数据类型,并跟一对可选的方括号[ ]里面为要求的元素数。它返回一个指向内存块开始位置的指针。其形式为:
pointer = new type
或者
pointer = new type [elements]
第一个表达式用来给一个单元素的数据类型分配内存。第二个表达式用来给一个数组分配内存。
删除操作符delete
既然动态分配的内存只是在程序运行的某一具体阶段才有用,那么一旦它不再被需要时就应该被释放,以便给后面的内存申请使用。操作符delete 因此而产生,它的形式是:
delete pointer;
或
delete [ ] pointer;
第一种表达形式用来删除给单个元素分配的内存,第二种表达形式用来删除多元素(数组)的内存分配。在多数编译器中两种表达式等价,使用没有区别, 虽然它们实际上是两种不同的操作,需要考虑操作符重载overloading
函数malloc
这是给指针动态分配内存的通用函数。它的原型是:
void * malloc (size_t nbytes);
其中nbytes 是我们想要给指针分配的内存字节数。这个函数返回一个void*类型的指针,因此我们需要用类型转换(type cast)来把它转换成目标指针所需要的数据类型,例如:
char * ronny;
ronny = (char *) malloc (10);
这个例子将一个指向10个字节可用空间的指针赋给ronny。当我们想给一组除char 以外的类型(不是1字节长度的)的数值分配内存的时候,我们需要用元素数乘以每个元素的长度来确定所需内存的大小。幸运的是我们有操作符sizeof,它可以返回一个具体数据类型的长度。
int * bobby;
bobby = (int *) malloc (5 * sizeof(int));
这一小段代码将一个指向可存储5个int型整数的内存块的指针赋给bobby,它的实际长度可能是 2,4或更多字节数,取决于程序是在什么操作系统下被编译的。
函数calloc
calloc 与malloc 在操作上非常相似,他们主要的区别是在原型上:
void * calloc (size_t nelements, size_tsize);
因为它接收2个参数而不是1个。这两个参数相乘被用来计算所需内存块的总长度。通常第一个参数(nelements)是元素的个数,第二个参数 (size) 被用来表示每个元素的长度。例如,我们可以像下面这样用calloc定义bobby:
int * bobby;
bobby = (int *) calloc (5, sizeof(int));
malloc 和calloc的另一点不同在于calloc 会将所有的元素初始化为0。
联合体union 内部的元素占用同一块内存空间(占用空间最大的那个元素的内存空间),
枚举enum
枚举类型在编译时是被编译为整型数值的,而它的数值列表可以是任何指定的整型常量,如果没有指定常量,枚举中第一个列出的可能值为0,后面的每一个值为前面值加1,
类:一种将数据和函数组织在同一个结构里的逻辑方法,定义类的关键字为class,其功能与C语言中的struct类似,不同之处是class可以包含函数,而不像struct只能包含数据元素;
类定义的形式是:
class class_name {
permission_label_1:
member1;
permission_label_2:
member2;
...
} object_name;
其中class_name是类的名称(用户自定义的类型),而可选项object_name是一个或几个对象(object)标识,Class的声明体包含成员members,成员可以是数据或者函数定义,同时也可以包括允许范围标识:private(默认),public或protected
private:class的private成员,只有同一个class的其他成员或该class的friend class可以访问这些成员;
protected:class的protected成员,只有同一个class的其他成员或该class的friend class或该class的子类derived classes可以访问这些成员;
public:class的public成员,任何可以看到这个class的地方都可以访问这些成员;
如果我们定义在一个class的成员的时候没有声明其允许范围,这些成员将默认为private范围;
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area (void);
} rect;
x,y是class的private成员,只能在class的其他成员引用;
// classes example
#include <iostream.h>
class CRectangle {
int x, y;
public:
void set_values (int,int);
int area (void) {return(x*y);}
};
void CRectangle::set_values (int a, int b) {
x = a;
y = b;
}
int main () {
CRectangle rect, rectb;
rect.set_values (3,4);
rectb.set_values (5,6);
cout << "rect area: " << rect.area() << endl;
cout << "rectb area: " << rectb.area() <<endl;
}
双冒号::是范围操作符,它是用来在一个class之外定义该class的成员,
area()函数的实现已经在类内部定义了,而set_values()只是定义原型,而其实现在class之外定义;
这种在class之外定义其成员的情况必须使用范围操作符::;
注意:调用函数rect.area()与调用rectb.area()所得到的结果是不一样的,这是因为每一个class CRectangle的对象都拥有自己的变量x和y,以及它自己的函数set_value()和area();
这是基于对象(object)和面向对象编程的概念的,这个概念中,数据和函数是对象(object)的属性,而不是像以前在结构化编程中所认为的对象是函数参数,;
构造函数和析构函数
对象在生成过程中通常需要初始化变量和分配动态内存,以便我们能够操作,或防止在执行结果返回意外结果,
为此,一个class可以包含一个特殊的函数:构造函数constructor,它可以通过声明一个与class同名的函数来定义,当且仅当在要生成一个class的新的实例的时候,也就是当且仅当声明一个新的对象,或给该class的一个对象分配内存的时候,这个构造函数会自动被调用;
构造函数的原型和实现中都没有返回值(returnvalue),也没有void 类型声明。构造函数必须这样写。一个构造函数永远没有返回值,也不用声明void,就像我们在前面的例子中看到的。
CRectangle::CRectangle (int a, int b) {
width = new int;
height = new int;
*width = a;
*height = b;
}
析构函数Destructor 完成相反的功能。它在objects被从内存中释放的时候被自动调用。释放可能是因为它存在的范围已经结束了(例如,如果object被定义为一个函数内的本地(local)对象变量,而该函数结束了);或者是因为它是一个动态分配的对象,而被使用操作符delete释放了。
析构函数必须与class同名,加水波号tilde (~) 前缀,必须无返回值。
CRectangle::~CRectangle () {
delete width;
delete height;
}
构造函数重载(overloading constructors)
一个构造函数可以被多次重装为同样名字的函数,但有不同的参数类型和个数,编译器会调用与在调用时刻要求的参数类型和个数一样的那个函数,在这里则是调用与类对象被声明时一样的那个构造函数,
实际上,当我们定义一个class而没有明确定义构造函数的时候,编译器会自动假设两个重载的构造函数 (默认构造函数"default constructor" 和复制构造函数"copy constructor")
它是一个没有任何参数的构造函数,被定义为nop,什么都不做;
#include <iostream.h>
Class CRectangle {
int width, height;
public:
CRectangle ();
CRectangle (int,int);
int area (void) {return (width*height);}
};
CRectangle::CRectangle () {
width = 5;
height = 5;
}
CRectangle::CRectangle (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle rect (3,4);
CRectangle rectb;
cout << "rect area: " << rect.area() <<endl;
cout << "rectb area: " << rectb.area() <<endl;
}
rectarea: 12
rectb area: 25在上面的例子中,rectb 被声明的时候没有参数,所以它被使用没有参数的构造函数进行初始化,也就是width 和height 都被赋值为5。
注意在我们声明一个新的object的时候,如果不想传入参数,则不需要写括号():
CRectangle rectb; // right
CRectangle rectb(); // wrong!
类的指针(pointers to classes)
类也是可以有指针的,类一旦被定义就成为一种有效的数据类型,因此只需要用类的名字作为指针的名字就可以了,
比如:
CRectangle * prect;
是一个指向classCRectangle类型的对象的指针。
就像数据结构中的情况一样,要想直接引用一个由指针指向的对象(object)中的成员,需要使用操作符 ->
#include <iostream.h>
class CRectangle {
int width, height;
public:
void set_values (int, int);
int area (void) {return (width * height);}
};
void CRectangle::set_values (int a, int b) {
width = a;
height = b;
}
int main () {
CRectangle a, *b, *c;
CRectangle * d = new CRectangle[2];
b= new CRectangle;
c= &a;
a.set_values (1,2);
b->set_values (3,4);
d->set_values (5,6);
d[1].set_values (7,8);
cout << "a area: " << a.area() << endl;
cout << "*b area: " << b->area() << endl;
cout << "*c area: " << c->area() << endl;
cout << "d[0] area: " << d[0].area() <<endl;
cout << "d[1] area: " << d[1].area() <<endl;
return 0;
}
aarea: 2
*b area: 12
*c area: 2
d[0] area: 30
d[1] area: 56
以下是怎样读前面例子中出现的一些指针和类操作符 (*, &, ., ->, [ ]):
? *x读作: pointed by x (由x指向的)
? &x读作: address of x(x的地址)
? x.y读作: member y of object x (对象x的成员y)
? (*x).y读作: member y of objectpointed by x(由x指向的对象的成员y)
? x->y读作: member y of objectpointed by x (同上一个等价)
? x[0]读作: first object pointedby x(由x指向的第一个对象)
? x[1]读作: second object pointedby x(由x指向的第二个对象)
? x[n] 读作: (n+1)th object pointed byx(由x指向的第n+1个对象)
由关键字struct和union定义的类
类不仅可以用关键字class来定义,也可以用struct或union来定义。
因为在C++中类和数据结构的概念太相似了,所以这两个关键字struct和class的作用几乎是一样的(也就是说在C++中struct定义的类也可以有成员函数,而不仅仅有数据成员)。两者定义的类的唯一区别在于由class定义的类所有成员的默认访问权限为private,而struct定义的类所有成员默认访问权限为public。除此之外,两个关键字的作用是相同的。
union的概念与struct和class定义的类不同, 因为union在同一时间只能存储一个数据成员。但是由union定义的类也是可以有成员函数的。union定义的类访问权限默认为public。
一个更值得推荐的构造函数定义应该像下面这样:
CVector ( ) { x=0; y=0; };
静态成员(Staticmembers)
一个class 可以包含静态成员(static members),可以是数据,也可以是函数。
一个class的静态数据成员也被称作类变量"class variables",因为它们的内容不依赖于某个对象,对同一个class的所有object具有相同的值。
友元函数
在前面的章节中我们已经看到了对class的不同成员存在3个层次的内部保护:public, protected 和 private。在成员为 protected 和 private的情况下,它们不能够被从所在的class以外的部分引用。然而,这个规则可以通过在一个class中使用关键字friend来绕过,这样我们可以允许一个外部函数获得访问class的protected 和 private 成员的能力。
为了实现允许一个外部函数访问class的private和protected 成员,我们必须在class内部用关键字friend来声明该外部函数的原型,以指定允许该函数共享class的成员。class CRectangle {
int width, height;
public:
void set_values (int, int);
int area (void) {return (width * height);}
friend CRectangle duplicate (CRectangle);
};
函数duplicate是CRectangle的friend,因此在该函数之内,我们可以访问CRectangle 类型的各个object的成员 width 和 height。注意,在 duplicate()的声明中,及其在后面main()里被调用的时候,我们并没有把duplicate 当作class CRectangle的成员,它不是。
friend 函数可以被用来实现两个不同class之间的操作。广义来说,使用friend函数是面向对象编程之外的方法,因此,如果可能,应尽量使用class的成员函数来完成这些操作。比如在以上的例子中,将函数duplicate() 集成在class CRectangle 可以使程序更短。
友元类(friend classes)
类的一个重要特征是继承,这使得我们可以基于一个类生产另一个类的对象,以便使后者拥有前者的某些成员,
class derived_class_name: publicbase_class_name;
这里derived_class_name 为子类(derivedclass)名称,base_class_name为基类(baseclass)名称。public也可以根据需要换为protected 或private,描述了被继承的成员的访问权限,class CRectangle: public CPolygon;
这里关键字public 表示新的类(CRectangle)从基类(CPolygon)所继承的成员必须获得最低程度保护。这种被继承成员的访问限制的最低程度可以通过使用 protected 或private而不是public来改变。class daughter: protected mother;
这将使得protected 成为daughter 从mother处继承的成员的最低访问限制。也就是说,原来mother 中的所有public 成员到daughter 中将会成为protected 成员,这是它们能够被继承的最低访问限制。当然这并不是限制daughter 不能有它自己的public 成员。最低访问权限限制只是建立在从mother中 继承的成员上的。
如果没有明确写出访问限制,所有由关键字class 生成的类被默认为private ,而所有由关键字struct 生成的类被默认为public。
多态:polymorphism
#line
当编译一段程序的时候,如果有错误发生,编译器就会再错误前面显示出错文件的名称以及文件中的第几行发生的错误;
指令#line可以使我们对这2点进行控制,也就是说当出错时显示文件中的行数以及我们希望显示的文件名,格式是#line number "filename"
#error指令 这个指令将中断编译过程并返回一个参数中定义的出错信息;
#ifndef __cplusplus
#error A C++ compiler is required
#endif
这个例子中如果__cplusplus 没有被定义就会中断编译过程。
#pragma 这个指令是用来对编译器进行配置的,针对所用的平台和编译器而有所不同,
设定编译器的状态,或者指示编译器完成某些特定的动作
#pragma message(“消息文本”)
|
1
2
3
|
#ifdef _X86
#pragma message("_X86macroactivated!")
#endif
|
以下宏名称在任何时候都是定义好的:
macro value
__LINE__ 整数值,表示当前正在编译的行在源文件中的行数。
__FILE__ 字符串,表示被编译的源文件的文件名。
__DATE__ 一个格式为 "Mmm dd yyyy" 的字符串,存储编译开始的日期。
__TIME__ 一个格式为 "hh:mm:ss" 的字符串,存储编译开始的时间。
__cplusplus 整数值,所有C++编译器都定义了这个常量为某个值。如果这个编译器是完全遵守C++标准的,它的值应该等于或大于199711L,具体值取决于它遵守的是哪个版本的标准。