类和对象
2.1 类
2.1.1 类的定义
类由三部分组成:类名、数据成员和成员函数。
class 类名 {
private:
// 私有数据成员和成员函数
public:
// 公有数据成员和成员函数
protected:
// 保护的数据成员和成员函数
};
有关类定义的几点说明:
类定义实例
class Tdate { // 定义日期类
public: // 定义公有成员函数
void Set(int m,int d,int y); // 置日期值
int IsLeapYear(); // 判是否闰年
void Print(); // 输出日期值
private: // 定义私有数据成员
int month;
int day;
int year;
}; // 类定义体的结束
2.1.2 类中成员函数的定义
类的数据成员说明对象的特征,而成员函数决定对象的操作行为。成员函数是程序算法实现的部分,是对封装的数据进行操作的唯一途径。类的成员函数有两种定义方法:外联定义和内联定义。
1. 外联成员函数(外联函数)
外联定义成员函数是指在类定义体中声明成员函数,而在类外定义成员函数。作用域区分符::指明一个成员函数或数据成员所在的类。::前若不跟类名,则成为全局数据或全局函数(非成员函数)。
在类外定义成员函数的具体形式为:
返回值类型 类名::成员函数名(形式参数表){
// 函数体
}
例如:
void Tdate::Set(int m, int d, int y) { // 置日期值
month = m; day = d; year = y;
}
int Tdate::IsLeapYear() { // 判是否闰年
return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
}
void Tdate::Print() { // 输出日期值
cout << month << "/" << day << "/" << year << endl;
}
2. 内联成员函数(内联函数、内部函数、内置函数)
内联函数是指程序在编译时将函数的代码插入在函数的每个调用处,作为函数体的内部扩展,用来避免函数调用机制所带来的开销,提高程序的执行效率。可以将那些仅由少数几条简单代码组成,却在程序中被频繁调用的函数定义为内联函数。
内联函数有两种定义方法,一种方法是在类定义体内定义成员函数,另一种方法是使用inline关键字。
(1) 在类定义体内定义内联函数(隐式声明)
class Tdate {
public:
void Set(int m,int d,int y) { // 置日期值
month=m; day=d; year=y;
}
private:
int month;
int day;
int year;
};
(2) 使用关键字inline定义内联成员函数(显式声明)
inline void Tdate::Set(int m, int d, int y) {
month = m; day = d; year = y;
}
或
void inline Tdate::Set(int m, int d, int y) {
month = m; day = d; year = y;
}
3.C++程序结构
一般情况下,一个模块由规范说明和实现两部分组成。规范说明部分描述一个模块与其它模块的接口,而实现部分则是模块的实现细节。模块中的说明部分作为一个单独的文件存放起来,这个文件被称为头文件,其扩展名为“.h”;而实现部分可能由多个扩展名为“.cpp”的文件组成。一般一个较大的文件可以分为三个文件来存放:
(1) 将类的说明作为一个头文件来存放。内联函数原型和定义一般归入头文件;
(2) 类的实现部分单独组成一个文件,用来存放类的成员函数的定义;
(3) 在main()中实现类的应用。
2.2 对象
1.对象的基本概念
2. 对象的定义
(1) 方法一:在定义类的同时直接定义
class location {
private:
int x, y;
public:
void init(int x0, int y0);
int Getx( void );
int Gety( void );
}dot1, dot2;
(2) 方法二:在使用时定义对象
类名 标识符, ... , 标识符;如:location dot1, dot2;
3.成员的访问
(1) 通过对象调用成员
格式:对象名.公有成员
其中,.称为对象选择符,简称点运算符。
(2) 通过指向对象的指针调用成员
格式:指向对象的指针->成员 或 (*对象指针名).公有成员
(3) 通过对象的引用调用成员
格式:对象的引用.成员
需要注意,只有用public定义的公有成员才能使用圆点操作符访问。对象中的私有成员是类中隐藏的数据,类的外部不能访问对象的私有成员,只能通过该类的公有成员函数来访问它们。
class Myclock {
private:
int hour, minute, second;
public:
void init();
void updata();
void display();
};
Myclock clock, *pclock; // 定义对象clock和指向Myclock类对象的指针pclock
clock.init(); // 通过对象访问公有成员函数
pclock = &clock; // 指针pclock指向对象clock
pclock->display(); // 通过指针访问成员函数
clock.hour = 4; // 错误,因为对象不能访问其私有成员
对象成员的访问举例
#include <iostream>
#include "tdate.h"
using namespace std;
void someFunc(Tdate& refs) {
refs.print(); // 通过对象的引用调用成员函数
if(refs.Isleapyear()) // 通过对象的引用调用成员函数
cout << "error\n";
else
cout << "right\n";
}
void main() { // 类的应用部分
Tdate s, *pTdate = &s; // pTdate为指向Tdate类对象的指针
s.set(2, 15, 1998); // 通过对象调用成员函数
pTdate->print(); // 通过指向对象的指针调用成员函数
if((*pTdate).Isleapyear()) // 通过指向对象的指针调用成员函数
cout << "error\n";
else
cout << "right\n";
someFunc(s); // 对象的地址传给引用
}
程序的运行结果为:
2/15/1998
right
2/15/1998
right
堆栈CStack类的实现,该类用于存储字符
#include<iostream>
using namespace std;
const int SIZE = 10; // 存储的最多字符数
class Cstack {
private:
char stk[SIZE];
int position;
public:
void init() { position = 0; }
char push (char ch);
char pop();
};
char Cstack::push(char ch) {
if(position == SIZE) {
cout << "\n栈已满 \n";
return 0;
}
stk[position++] = ch;
return ch;
}
char Cstack::pop() {
if (position == 0) {
cout << "\n栈已空" << endl;
return 0;
}
return stk[--position];
}
int main() {
Cstack s;
s.init();
char ch;
cout << "请输入字符:" << endl;
cin >> ch;
while((ch != '#') && s.push(ch))
cin >> ch;
cout << "\n现在输出栈内数据\n";
while(ch = s.pop())
cout << ch;
return 0;
}
程序的运行结果为:
请输入字符:
01234567896
栈已满
现在输出栈内数据
9876543210
栈已空
4.名字解析和this指针
(1) 名字解析
在调用成员函数时,通常使用缩写形式,如上例中的语句s.set(2, 15, 1998);就是s.Tdate::set(2, 15, 1998);的缩写,因此可以定义两个或多个类的具有相同名字的成员而不会产生二义性。
(2) this指针
当一个成员函数被调用时,自动向它传递一个隐含的参数,该参数是一个指向接受该函数调用的对象的指针,在程序中可以使用关键字this来引用该指针,因此称该指针为this指针。this指针是C++实现封装的一种机制,它将成员和用于操作这些成员的成员函数连接在一起。
this指针
void Tdate::set(int m, int d, int y) { // 置日期值
month = m; day = d; year = y;
}
void Tdate::set(int m, int d, int y) { // 置日期值
this->month = m; this->day = d; this->year = y;
}
使用this指针,保证了每个对象可以拥有不同的数据成员,但处理这些数据成员的代码可以被所有的对象共享。
5.带缺省参数的成员函数和重载成员函数
同普通函数一样,类的成员函数也可以是带缺省值的函数,其调用规则同普通函数。成员函数也可以是重载函数,类的成员函数的重载与全局函数的重载方法相同。
带缺省参数的成员函数
#include <iostream>
using namespace std;
class Tdate {
public:
void Set(int m = 5, int d = 16, int y = 1991) { // 置日期值
month = m; day = d; year = y;
}
void Print() { // 输出日期值
cout << month << "/" << day << "/" << year << endl;
}
private:
int month;
int day;
int year;
};
int main() {
Tdate a, b, c;
a.Set(4, 12, 1996);
b.Set(3);
c.Set(8, 10);
a.Print();
b.Print();
c.Print();
return 0;
}
程序的运行结果为:
4/12/1996
3/16/1991
8/10/1991
重载成员函数
#include<iostream>
using namespace std;
class cube {
public:
int volume(int ht, int wd) {
return ht * wd;
}
int volume(int ht, int wd, int dp) {
height = ht;
width = wd;
depth = dp;
return height * width * depth;
}
private:
int height, width, depth;
};
int main() {
cube cube1;
cout << cube1.volume(10, 20) << endl;
cout << cube1.volume(10, 20, 30) << endl;
return 0;
}
程序的运行结果为:
200
6000
2.3 构造函数和析构函数
2.3.1 构造函数
对象的初始化是指对象数据成员的初始化,在使用对象前,一定要初始化。由于数据成员一般为私有的(private),所以不能直接赋值。对对象初始化有以下两种方法:类中提供一个普通成员函数来初始化,但是会造成使用上的不便(使用对象前必须显式调用该函数)和不安全(未调用初始化函数就使用对象)。另外一种方法是使用构造函数对对象进行初始化。
1. 构造函数(constructor)
构造函数是一个与类同名,没有返回值的特殊成员函数。一般用于初始化类的数据成员,每当创建一个对象时(包括使用new动态创建对象),编译系统就自动调用构造函数。构造函数既可在类外定义,也可作为内联函数在类内定义。
构造函数定义了创建对象的方法,提供了初始化对象的一种简便手段。构造函数的声明格式为:<类名>::构造函数名(<形式参数表>)可以重载构造函数
构造函数的定义和调用
#include <iostream>
using namespace std;
class queue {
int q[100];
int sloc,rloc;
public:
queue();
void qput(int i);
int qget();
};
queue::queue() {
sloc = rloc = 0;
cout << "queue initialized\n";
}
void queue::qput(int i) {
if (sloc == 100) {
cout << "queue is full\n";
return;
}
sloc++;
q[sloc] = i;
}
int queue::qget( ) {
if (rloc==sloc) {
cout<< "queue is empty\n";
return 0;
}
rloc++;
return q[rloc];
}
int main( ) {
queue a, b;
a.qput(10);
b.qput(20);
a.qput(20);
b.qput(19);
cout << a.qget( ) << " ";
cout << a.qget( ) << "\n";
cout << b.qget( ) << " ";
cout << b.qget( ) << "\n";
return 0;
}
程序的执行结果为:
queue initialized
queue initialized
10 20
20 19
说明:从此例可以看出,在main()函数中,构造函数queue()没有被显式调用的地方。正如我们在前面提到过的,构造函数是在定义对象时被系统自动调用的,也就是说在定义对象a,b的同时a.queue::queue()和b.queue::queue()被自动调用执行。
构造函数的重载
#include <iostream>
using namespace std;
class test {
private:
int num;
float f1;
public:
test();
test(int n, float f); // 参数化的构造函数
int getint(){ return num; }
float getfloat(){ return f1; }
};
test::test() {
cout << "Initializing default" << endl;
num = 0;
f1 = 0.0;
}
test::test(int n, float f) {
cout << "Initializing " << n << ", " << f << endl;
num = n;
f1 = f;
}
int main() {
test x; // 调用无参的构造函数
test y(10, 21.5); // 调用带两个参数的构造函数
test *px = new test; // 调用无参的构造函数
test *py = new test(10, 21.5); // 调用带两个参数的构造函数
return 0;
}
程序的执行结果为:
Initializing default
Initializing 10, 21.5
Initializing default
Initializing 10, 21.5
类的构造函数一般是公有的(public),但有时也声明为私有的,其作用是限制创建该类对象的范围,即只能在本类和友元中创建该类对象。
2.带缺省参数的构造函数
构造函数也可以使用缺省参数,但要注意,必须保证函数形式参数缺省后,函数形式不能与其它构造函数完全相同。即在使用带缺省参数的构造函数时,要注意避免二义性。所带的参数个数或参数类型必须有所不同,否则系统调用时会出现二义性。
带缺省参数的构造函数
#include <iostream>
using namespace std;
class Tdate {
public:
Tdate(int m = 5,int d = 16,int y = 1990) {
month = m; day = d; year = y;
cout << month << "/" << day << "/" << year << endl;
}
private:
int month;
int day;
int year;
};
int main() {
Tdate aday;
Tdate bday(2);
Tdate cday(3, 12);
Tdate dday(1, 2, 1998);
return 0;
}
程序的运行结果为:
5/16/1990
2/16/1990
3/12/1990
1/2/1998
3.缺省构造函数(默认构造函数)和对象数组
(1) 缺省构造函数
C++规定,每个类必须有一个构造函数,没有构造函数,就不能创建任何对象。若用户未显式定义一个类的构造函数,则C++提供一个缺省的构造函数,也叫默认构造函数,该缺省构造函数是个无参构造函数,它仅负责创建对象,而不做任何初始化工作。只要一个类定义了一个构造函数(不一定是无参构造函数),C++就不再提供默认的构造函数。如果为类定义了一个带参数的构造函数,还想要使用无参构造函数,则必须自己定义。与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0,否则,对象值是随机的。
栗子:缺省构造函数
#include<iostream>
#include <string>
using namespace std;
class Student {
public:
Student(char* pName) {
cout<<"call one parameter constructor"<<endl;
strncpy(name,pName,sizeof(name));
name[sizeof(name)-1]= '\0';
cout<<"the name is "<<name<<endl;
}
Student() {
cout << "call no parameter constructor" << endl;
}
/*
* 注意,上面的无参构造函数不能省略,因为在main()中创建无参对象noName时
* 会调用此函数,如果省略掉此条语句则编译时会显示以下的错误信息:
* error C2512: 'Student' : no appropriate default constructor available
*/
protected:
char name[20];
};
int main(){
Student noName;
Student ss("Jenny");
return 0;
}
程序的运行结果为:
call no parameter constructor
call one parameter constructor
the name is Jenny
(2) 对象数组
对象数组是指每一个数组元素都是对象的数组,也就是说,若某一个类有若干个对象,我们把这一系列被创建的对象用一个数组来存放。
若要说明一个带有构造函数的类的对象数组,这个类一般情况下会含有一个不带参数的构造函数或带有缺省参数的构造函数。因为当说明一个对象数组时,程序为这个对象数组的每个元素对象调用了一次缺省构造函数来初始化每个元素对象,举个栗子:
// test.h
class test {
private:
int num;
float f1;
public:
test();
test(int n, float f); // 参数化的构造函数
int getint(){ return num; }
float getfloat(){ return f1; }
};
// test.cpp
#include "test.h"
#include<iostream>
using namespace std;
test::test() {
cout<<"Initializing default"<<endl;
num = 0;
f1 = 0.0;
}
test::test(int n, float f) {
cout<<"Initializing "<<n<<", "<<f<<endl;
num = n;
f1 = f;
}
// ex1.cpp
#include <iostream>
#include "test.h"
using namespace std;
int main() {
cout << "the main function:" << endl;
test array[5];
cout << "the second element of array is " << array[1].getint()
<< " " << array[1].getfloat() << endl;
return 0;
}
程序的运行结果为:
the main function:
Initializing default
Initializing default
Initializing default
Initializing default
Initializing default
the second element of array is 0 0
当没有为一个类定义有缺省构造函数时,在说明对象数组时必须提供初始值,举个栗子:
#include<iostream>
using namespace std;
class test {
private:
int num;
float f1;
public:
test(int n);
test(int n, float f);
};
inline test::test(int n) {
cout << "Initializing " << n << endl;
num = n;
}
test::test(int n, float f) {
cout << "Initializing " << n << ", " << f << endl;
num = n;
f1 = f;
}
int main() {
// test array0[3]; 若加上此语句,则编译时会出现如下的错误信息:
// error C2512: 'test' : no appropriate default constructor available
test array1[3]={1,2,3};
test array2[]={test(2,3.5),test(4)};
test array3[] = {test(5.5,6.5),test(7,8.5)};
// array3有2个元素
test array4[] = {test(5.5, 6.5), 7.5, 8.5};
// array4有3个元素
return 0;
}
程序的运行结果为:
Initializing 1
Initializing 2
Initializing 3
Initializing 2, 3.5
Initializing 4
Initializing 5, 6.5
Initializing 7, 8.5
4. 拷贝构造函数(复制构造函数)
(1) 拷贝构造函数
//下面定义了一个cat类和cat类的拷贝构造函数:
class cat {
private:
int age;
float weight;
char *color;
public:
cat();
cat(cat &); // 拷贝构造函数的声明
void play();
void hunt();
};
cat::cat(cat &other) {
age = other.age;
weight = other.weight;
color = other.color;
}
自动调用拷贝构造函数的四种情况
① 用类的一个对象去初始化另一个对象
cat cat1;
cat cat2(cat1); // 创建cat2时系统自动调用拷贝构造函数,用cat1初始化cat2。
② 用类的一个对象去初始化另一个对象时的另外一种形式
Cat cat2 = cat1; // 注意并非cat cat1, cat2; cat2 = cat1;
③ 对象作为函数参数传递时,调用拷贝构造函数。
f(cat a){ } // 定义f函数,形参为cat类对象
cat b; // 定义对象b
f(b); // 进行f函数调用时,系统自动调用拷贝构造函数
④ 如果函数的返回值是类的对象,函数调用返回时,调用拷贝构造函数。
cat f() { // 定义f函数,函数的返回值为cat类的对象
cat a;
…
return a;
}
cat b; // 定义对象b
b = f(); // 调用f函数,系统自动调用拷贝构造函数
(2) 深拷贝构造函数和浅拷贝构造函数
拷贝构造函数分为两种,深拷贝和浅拷贝构造函数。由C++提供的默认拷贝构造函数只是对对象进行浅拷贝复制(逐个成员依次拷贝),即只复制对象空间而不复制资源。如图1所示为浅拷贝的两种情况。
一般情况下,只需使用系统提供的浅拷贝构造函数即可,但是如果对象的数据成员包括指向堆空间的指针,就不能使用这种拷贝方式,因为两个对象都拥有同一个资源,对象析构时,该资源将经历两次资源返还,此时必须自定义深拷贝构造函数,为创建的对象分配堆空间,否则会出现动态分配的指针变量悬空的情况。深拷贝需要同时复制对象空间和资源,如图2所示。
深拷贝构造函数栗子
#include<string>
#include<iostream>
using namespace std;
class Person
{
public:
Person(char *na) // 构造函数
{
cout<<"call constructor"<<endl;
name=new char[strlen(na)+1]; // 使用new进行动态内存分配
if(name!=0)
{strcpy(name,na);}
}
Person(Person&p) // 深拷贝构造函数
{
cout<<"call copy constructor"<<endl;
name=new char[strlen(p.name)+1]; // 复制资源
if(name!=0)
strcpy(name,p.name); // 复制对象空间
}
void printname()
{
cout<<name<<endl;
}
~Person(){delete name;} // 析构函数的定义,参见3.3.2小节
private:
char *name;
}; // 类定义的结束
void main()
{ Person wang("wang");
Person li(wang);
wang.printname();
li.printname();
}
程序的运行结果为:
call constructor
call copy constructor
wang
wang
在主函数main()中,定义了Person类的对象wang,在定义对象li时调用了深拷贝构造函数,如果用户没有定义深拷贝构造函数,则编译系统会调用缺省的浅拷贝构造函数,这样,在程序运行时会出现错误。
所以,使用拷贝构造函数时应考虑是使用深拷贝还是浅拷贝,当数据成员有使用new动态分配内存空间,在析构函数中使用delete进行动态内存空间的释放,以及对赋值 = 运算符进行重载时,应该自定义深拷贝构造函数。
5. 数据成员的初始化
构造函数可以采用以下几种不同的形式对数据成员初始化:
(1) 在构造函数的函数体中进行初始化。
(2) 使用构造初始化表对数据成员进行初始化,其格式为:
<类名>::<构造函数>(<参数表>):<变量1>(<初值1>), …, <变量n>(<初值n>) {
//函数体
}
如:
Circle::Circle(float r):radius(r) {
}
成员初始化的次序取决于它们在类定义中的声明次序,与它们在成员初始化表中的次序无关。
常量和引用的初始化必须放在构造函数正在建立数据成员结构的时候,也就是放在构造函数的冒号后面,栗子如下。
class SillyClass {
public:
SillyClass(int&i):ten(10),refI(i) {
}
protected:
const int ten; // 常量数据成员
int &refI; // 引用数据成员
};
void main() {
int i ;
SillyClass sc(i);
}
//若把构造函数定义为下列方式:
SillyClass() {
ten = 10; // error ,常量不能重新赋值
refI = i; // error , 引用不能重新指派
};
/*
*则程序编译时会出现如下的错误提示:
*error C2758: 'ten' : must be initialized in constructor base/member initializer list
*error C2758: 'refI' : must be initialized in constructor base/memberinitializer list
*/
(3) 混合初始化,如:
Student::Student(int n, int a, char*pname):number(n),age(a){
strcpy(name, pname);
}
(4) 使用拷贝构造函数进行初始化(如上所述)
6. 类类型和基本数据类型的转换
(1) 构造函数用作类型转换
通过构造函数用作类型转换可以实现基本数据类型转换为类类型。
通过构造函数进行类型转换必须有一个前提,那就是此类一定要有一个只带一个参数的构造函数,这样才可以实现从参数类型向该类类型的转换,并且这种转换是隐式的。如:
class A {
…
public:
A();
A(int);
};
…
f(A a) {// f函数的定义;f函数的形参为A类的对象
…
}
f(1); // f函数的调用;进行f函数调用时先构造一个A类对象,
// 调用A::A(int)进行隐式类型转换,然后把它传给函数f的形式参数。
(2) 类类型转换函数
类类型转换函数用来将类类型向基本类型转换。
① 在类定义体中声明 operator type();
其中type为要转换的基本类型名,此函数既没有参数,又没有返回类型,但在函数体中必须返回具有type类型的一个对象。
② 定义转换函数的函数体
类名::opertor type() {
…
return type类型的值;
}
③ 使用类型转换函数:使用类型转换函数与对基本类型进行强制转换时一样,就象是一种函数调用过程。
举个栗子:
#include<iostream>
using namespace std;
class RMB {
public:
RMB(double value = 0.0); // 构造函数用作类型转换
operator double() {
return yuan + jf / 100.0; // 类类型转换函数
}
void display() {
cout << (yuan + jf / 100.0) << endl;
}
protected:
unsigned int yuan;
unsigned int jf;
};
RMB::RMB(double value) {
yuan = value;
jf = ( value – yuan ) * 100 + 0.5;
}
int main() {
RMB d1(2.0), d2(1.5), d3;
d3 = RMB((double)d1 + (double)d2); // 显式转换
d3 = d1 + d2;
// 隐式转换,系统首先会调用类类型转换函数把对象d1和d2隐式转换为double数据类型,
// 然后进行相加,加完的结果再调用构造函数隐式转换为RMB类类型,赋给d3。
d3.display();
return 0;
}
程序的运行结果为:
3.5
2.3.2 析构函数
1. 析构函数被自动调用的三种情况
(1) 一个动态分配的对象被删除,即使用delete删除对象时,编译系统会自动调用析构函数;
(2) 程序运行结束时;
(3) 一个编译器生成的临时对象不再需要时。
2.析构函数的手工调用
构造函数只能由系统自动调用,而析构函数可以使用下述方法手工调用:
对象名.类名::析构函数名
3.析构函数与构造函数的调用顺序
构造函数和析构函数的调用顺序刚好相反,先构造的后析构。
栗子:析构函数和构造函数的调用顺序
#include <iostream>
#include <string.h>
using namespace std;
class Student {
public:
Student(char* pName = "no name", int ssId = 0) {
strncpy(name,pName,40);
name[39]= '\0';
id = ssId;
cout << "Constructing new student " << pName << endl;
}
Student(Student& s) { // 拷贝构造函数
cout << "Constructing copy of " << s.name << endl;
strcpy(name, "copy of ");
strcat(name, s.name);
id=s.id;
}
~Student() {
cout << "Destructing " << name << endl;
}
protected:
char name[40];
int id;
};
void fn(Student s) {
cout << "In function fn()\n"; // fn函数调用结束时,析构对象s
}
int main() {
Student randy("Randy", 1234); // 调用构造函数,创建对象randy
Student wang("wang", 5678); // 调用构造函数,创建对象wang
cout << "Calling fn()\n";
fn(randy); // 调用fn函数,参数传递时调用拷贝构造函数
cout << "Returned from fn()\n";
// 主函数调用结束时,先析构对象wang,再析构对象randy
return 0;
}
程序的运行结果为:
Constructing new student Randy
Constructing new student wang
Calling fn()
Constructing copy of Randy
In function fn()
Destructing copy of Randy
Returned from fn()
Destructing wang
Destructing Randy
2.4 类的聚集—对象成员
栗子:含有对象成员的类的析构函数和构造函数的调用顺序举例一。
#include <iostream>
#include <string.h>
using namespace std;
class StudentID {
public:
StudentID(int id = 0) { // 带缺省参数的构造函数
value = id;
cout << "Assigning student id " << value << endl;
}
~StudentID() {
cout << "Destructing id " << value << endl;
}
private:
int value;
};
class Student {
public:
Student(char* pName = "no name", int ssID = 0):id(ssID) {
cout << "Constructing student " << pName << endl;
strncpy(name, pName, sizeof(name));
name[sizeof(name)-1] = '\n';
}
~Student(){
cout << "Deconstructing student " << name << endl;
}
protected:
char name[20];
StudentID id; // 对象成员
};
int main() {
Student s("wang",9901);
Student t("li");
return 0;
}
程序的运行结果为:
Assigning student id 9901
Constructing student wang
Assigning student id 0
Constructing student li
Deconstructing student li
Destructing id 0
Deconstructing student wang
Destructing id 9901
栗子:含有对象成员的类的析构函数和构造函数的调用顺序举例二。
#include<iostream>
using namespace std;
class Student { // 学生类的定义
public:
Student() {
cout << "constructing student.\n";
semesHours = 100;
gpa = 3.5;
}
~Student() {
cout << "destructing student.\n";
}
protected:
int semesHours;
float gpa;
};
class Teacher { // 教师类的定义
public:
Teacher() {
cout << "constructing teacher.\n";
}
~Teacher() {
cout << "destructing teacher.\n";
}
};
class Tutorpair { // 帮教类的定义
public:
Tutorpair() {
cout << "constructing tutorpair.\n";
nomeeting = 0;
}
~Tutorpair() {
cout << "destructing tutorpair.\n";
}
protected:
Student student;
Teacher teacher;
int nomeeting; // 会晤时间
};
int main() {
Tutorpair tp;
cout << "back main.\n";
return 0;
}
程序的运行结果为:
constructing student.
constructing teacher.
constructing tutorpair.
back main.
destructing tutorpair.
destructing teacher.
destructing student.
2.5 静态成员
1.静态数据成员
静态数据成员被存放在内存的某一单元内,该类的所有对象都可以访问它。无论建立多少个该类的对象,都只有一个静态数据的拷贝。即使没有创建任何一个该类对象,类的静态成员在存储空间中也是存在的,可以通过名字解析运算符来直接访问。
含有静态数据成员的类在创建对象时不为静态数据成员分配存储空间,可以将静态数据成员看成是一个全局变量,将其封装在某个类中通常有两个目的:
(1) 限制该变量的作用范围。例如将其放在类的私有部分声明,则它只能由该类对象的函数成员直接访问。
(2) 将意义相关的全局变量和相关的操作物理地放在一起,可以增加程序的可读性和可维护性。
初始化在类体外进行。其格式如下:<数据类型><类名>::<静态数据成员名>=(初始值);
如果整个程序分为多个文件进行分块编译,则应该将类的声明放在头文件中,然后将静态成员的初始化放在某一个源文件中。
静态数据成员的使用
#include<iostream>
using namespace std;
class A{
static int i; // 定义静态数据成员
public:
A(){ i++; }
int list(){ return i; }
};
int A::i=0; // 请注意静态数据成员的初始化格式,无static关键字
int main() {
A a1,a2,a3;
cout << a1.list() << ',' << a2.list() << ',' << a3.list();
// 显示均为3(因为创建三个对象,三次使得静态数据成员加1)
return 0;
}
程序的运行结果为:
3,3,3
2.静态成员函数
(1) 静态成员函数无this指针,它是同类的所有对象共享的资源,只有一个共用的副本,因此它不能直接访问非静态的数据成员,必须要通过某个该类对象才能访问。而一般的成员函数中都含有一个this指针,指向对象自身,可以直接访问非静态的数据成员。
(2) 在静态成员函数中访问的基本上是静态数据成员或全局变量。
(3) 由于静态成员函数属于类独占的成员函数,因此访问静态成员函数的消息接收者不是类对象,而是类自身。在调用静态成员函数的前面,必须缀上对象名或类名,经常用类名。
(4) 一个类的静态成员函数与非静态成员函数不同,它不需要创建任何该类的对象就可以被调用。静态成员函数的使用虽然不针对某一个特定的对象,但在使用时系统中最好已经存在此类的对象,否则无意义。
(5) 静态成员函数不能是虚函数(虚函数的概念参见C++部分第5篇博客),若非静态成员函数和静态成员函数具有相同的名字和参数类型将是非法的。
栗子:静态数据成员和静态成员函数使用举例
#include <iostream>
#include <string.h>
using nsmaspace std;
class Student {
public:
Student(char* pName = "no name") {
cout << "create one student\n";
strncpy(name, pName, 40);
name[39] = '\0';
noOfStudents++; // 静态成员:每创建一个对象,学生人数增1
cout << noOfStudents << endl;
}
~Student() {
cout << "destruct one student\n";
noOfStudents--; // 每析构一个对象,学生人数减1
cout << noOfStudents << endl;
}
static int number() { // 静态成员函数
return noOfStudents;
}
protected:
static int noOfStudents; // 若写成noOfStudents=0;则非法
char name[40];
};
int Student::noOfStudents = 0;// 静态数据成员在类外分配空间和初始化
void fn() {
Student s1;
Student s2;
cout << Student::number() << endl;// 调用静态成员函数用类名引导
}
int main() {
fn();
cout << Student::number() << endl;// 调用静态成员函数用类名引导
return 0;
}
程序的运行结果为:
create one student
1
create one student
2
2
destruct one student
1
destruct one student
0
0
栗子:静态成员(学生链表的构建和使用)
#include <iostream>
#include <string.h>
using namespace std;
class Student {
public:
Student(char* pName);
~Student();
protected:
static Student* pFirst;
Student* pNext;
char name[40];
};
Student* Student::pFirst = 0;
Student::Student(char* pName) {
strncpy(name, pName, sizeof(name));
name[sizeof(name)-1] = '\0';
pNext = pFirst;
// 每新建一个结点(对象),就将其挂在链首
pFirst = this;
}
Student::~Student() {
cout << this->name << endl;
if (pFirst == this) {
// 如果要删除链首结点,则只要链首指针指向下一个
pFirst = pNext;
return;
}
for (Student* pS = pFirst; pS; pS = pS->pNext) {
if (pS->pNext == this) {
// 找到时,pS指向当前结点的前一结点
pS->pNext = pNext;
// pNext即this->pNext
return;
}
}
}
Student* fn() {
Student* pS = new Student("Jenny");
Student sb("Jone");
return pS;
}
int main() {
Student sa("Jamsa");
Student* sb = fn();
Student sc("Tracey");
delete sb;
return 0;
}
程序的运行结果为:
Jone
Jenny
Tracey
Jamsa
若原程序改为:
#include <iostream>
#include <string.h>
using namespace std;
class Student{
public:
Student(char* pName);
~Student();
Student(Student&s);
protected:
static Student* pFirst;
Student* pNext;
char name[40];
};
Student* Student::pFirst = 0;
Student::Student(char* pName) {
strncpy(name, pName, sizeof(name));
name[sizeof(name) - 1] = '\0';
pNext = pFirst;
// 每新建一个结点(对象),就将其挂在链首
pFirst = this;
cout << "constructor " << this->name << endl;
}
Student::Student(Student &s){
strncpy(name, s.name, sizeof(s.name));
strcat(name, "copy");
name[sizeof(name) - 1] = '\0';
pNext = pFirst;
// 每新建一个结点(对象),就将其挂在链首
pFirst = this;
cout << "copy constructor " << this->name << endl;
}
Student::~Student() {
cout << "destructor " << this->name << endl;
if (pFirst == this) {
// 如果要删除链首结点,则只要链首指针指向下一个
pFirst = pNext;
return;
}
for (Student* pS = pFirst; pS; pS = pS->pNext) {
if (pS->pNext == this) {
// 找到时,pS指向当前结点的前一结点
pS->pNext = pNext;
// pNext即this->pNext
return;
}
}
}
Student fn() {
Student pS("Jenny");
Student sb("Jone");
return pS;
}
int main(){
Student sa("Jamsa");
Student sb = fn();
Student sc("Tracey");
return 0;
}
则程序的运行结果为:
constructor Jamsa
constructor Jenny
constructor Jone
copy constructor Jennycopy
destructor Jone
destructor Jenny
constructor Tracey
destructor Tracey
destructor Jennycopy
destructor Jamsa
在上述修改程序的基础上,若再修改主函数中的语句,
#include <iostream>
#include <string.h>
using namespace std;
class Student{
public:
Student(char* pName);
~Student();
Student(Student&s);
protected:
static Student* pFirst;
Student* pNext;
char name[40];
};
Student* Student::pFirst = 0;
Student::Student(char* pName) {
strncpy(name, pName, sizeof(name));
name[sizeof(name) - 1] = '\0';
pNext = pFirst;
// 每新建一个结点(对象),就将其挂在链首
pFirst = this;
cout << "constructor " << this->name << endl;
}
Student::Student(Student &s) {
strncpy(name, s.name, sizeof(s.name));
strcat(name, "copy");
name[sizeof(name) - 1] = '\0';
pNext = pFirst;
// 每新建一个结点(对象),就将其挂在链首
pFirst = this;
cout << "copy constructor " << this->name << endl;
}
Student::~Student() {
cout << "destructor " << this->name << endl;
if (pFirst == this) {
// 如果要删除链首结点,则只要链首指针指向下一个
pFirst=pNext;
return;
}
for (Student* pS = pFirst; pS; pS = pS->pNext) {
if (pS->pNext == this) {
// 找到时,pS指向当前结点的前一结点
pS->pNext = pNext;
// pNext即this->pNext
return;
}
}
}
Student fn() {
Student pS("Jenny");
Student sb("Jone");
return pS;
}
int main() {
Student sa("Jamsa");
Student sb("aa");
sb = fn();
Student sc("Tracey");
return 0;
}
则程序的运行结果为:
constructor Jamsa
constructor aa
constructor Jenny
constructor Jone
copy constructor Jennycopy
destructor Jone
destructor Jenny
destructor Jennycopy
constructor Tracey
destructor Tracey
destructor Jennycopy
destructor Jamsa
2.6 指向类成员的指针
C++提供一种特殊的指针类型,它指向类的成员,而不是指向该类的一个对象中该成员的一个实例,这种指针称为指向类成员的指针。
1. 由类外指向类内的指针变量
语法声明格式:类型符 类名∷*指针名=类中数据成员地址描述;
类外指向类内的指针变量。
#include<iostream>
using namespace std;
class A {
public:
int i,*p;
A(){
i=10;
p=&i;
}
};
int A::*p = &A::i; // p是类外指针数据
int main() {
A aa,bb; // 按缺省构造函数的安排,aa、bb中的i初值都是10
(bb.*p)++; // 括号不可缺,否则编译器将理解为非法操作
--*aa.p;
cout << "AA:" << aa.*p << " BB:" << bb.*p << "\n";
cout << "AA:" << *aa.p << " BB:" << *bb.p << endl;
return 0;
}
程序的运行结果为:
AA:9 BB:11
AA:9 BB:11
例中的指针变量p出现在两处,一处是类A的构造函数中(p = &i),另一处是全局数据区中。在本例中它们彼此并不冲突,各行其事。要特别注意由类外指向类内的指针数据在使用时的写法:类所定义的对象名.指针名… 这种在类外定义的指向类内数据成员的指针在使用上由于受类的数据封装特性的限制,其所指成员只能处于public区中。
2.类外指向成员函数的指针数据
语法声明格式:类型符(类名∷*指针名)(参数类型表)=&类名∷函数名;
与指向数据成员的指针相同,这里的成员函数也只能在public区中声明。
类外指向成员函数的指针变量。
#include<iostream>
using namespace std;
class A {
int i;
public:
int set(int k){
i = ++k;
return i;
}
};
int main() {
int (A::*f)(int) = &A::set;
A aa;
cout << (aa.*f)(10) << endl; // 括号不能省略
return 0;
}
程序的运行结果为:
11
3.指向类内静态成员的指针
静态成员由于可与对象分离,唯一地存放于公用的全局区中,所以指向静态成员的指针也就简化了。与上面讲的指针的不同之处在于:
(1) C++语言不检查静态成员处于何处(实际全放在全局数据区);
(2) 必须在全局区用数据类型 类名∷静态变量名[=…];的格式来指明其作用域或初值。
栗子:指向静态成员的指针
#include<iostream>
using namespace std;
class A {
static int i;
friend class B;
public:
static void set(int k){
i = k;
i++;
}
};
class B {
public:
static void ds(int l){
int *p = &A::i;
cout << *p << "\n";
*p = l;
cout << *p;
}
};
int A::i=0;
void (*f1)(int) = &A::set;
void (*f2)(int) = &B::ds;
int main() {
f1(10);
f2(20);
return 0;
}
程序的运行结果为:
11
20
请特别注意例中对两种指针使用的格式。由于无需对象,所以使用时指针前的对象名可以全免。

浙公网安备 33010602011771号