C++自学 | 8 类 & 对象
本内容仅属于个人自学记录,欢迎交流和指正,请勿转载或发表不当言论。
主要学习网址:https://www.runoob.com/
8.1 Cplus/C++ 类 & 对象
1. 类定义:
类用于指定对象的形式,包含了数据表示法和用于处理数据的方法。类中的数据和方法称为类的成员,函数在一个类中也被称为类的成员。
定义类时,本质上是定义了一个数据类型的蓝图,这实际上并没有定义任何数据,但是定义了类的对象包括什么,以及可以在这个对象上执行哪些操作。
class class_name { public: double var1; double var2; };
其中,关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可以访问的。
2. 定义 C++ 对象:
类提供了对象的蓝图,基本上对象是根据类来创建的。声明类的对象,就像声明基本类型的变量。
class_name var1;
class_name var2;
对象 var1 和 var2 都有各自的数据成员。
3. 访问:
类的对象 的公共数据成员可以使用成员访问运算符(.)来访问。
【实例:
#include <iostream> using namespace std; class Box { public: double length; double breadth; double height; private: }; int main() { Box box1; Box box2; double volume = 0.0; //box1 详述 box1.height = 10.0; box1.length = 12.0; box1.breadth = 13.0; //box2 详述 box2.height = 5.0; box2.length = 6.0; box2.breadth = 7.0; volume = box1.height * box1.breadth * box1.length; cout << "Box1 的体积:" << volume << endl; volume = box2.height * box2.breadth * box2.length; cout << "Box2 的体积:" << volume << endl; return 0; }
8.2 Cplus/C++ 类成员函数
4. 类成员函数:
指把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
成员函数可以定义在类定义内部,或者但单独使用 范围解析运算符(::)来定义。在类定义中定义的成员函数把函数声明为 内联 的。
定义方法一:
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(void) { return length * breadth * height; } };
定义方法二:
class Box { public: double length; // 长度 double breadth; // 宽度 double height; // 高度 double getVolume(void);// 成员函数声明 }; double Box::getVolume(void) { return length * breadth * height; }
在第二种方法中,:: 运算符前必须使用类名,调用成员函数时在对象前使用 . 运算符:
Box mybox; myBox.getVolume(); //调用该对象的成员函数
其中 :: 运算符叫 作用域区分符,指明一个函数属于哪个类或者一个数据属于哪个类,如果前不跟类名,则表示 全局数据 或 全局函数(即非成员函数)。
int month;//全局变量 int day; int year; void Set(int m,int d,int y) { ::year=y; //给全局变量赋值,此处可省略 ::day=d; ::month=m; } Class Tdate { public: void Set(int m,int d,int y) //成员函数 { ::Set(m,d,y); //非成员函数 } private: int month; int day; int year; }
此外,定义在类中的成员函数默认都是内联的,如果在类中未给出成员的函数定义,又想内联该函数的话要在类外加上 inline(必须与函数定义体放在一起,才能使函数成为内联函数)。
【实例:
class A { public: void Foo(int x, int y) {} // 自动地称为内联函数 }
等同于
class A { public: void Foo(int x, int y); } inline void A::Fonn(int x, int y) {}
【实例:录入学生成绩系统
#include <iostream> #include <iomanip> #include <string> #include <cstdio> #include <cstring> using namespace std; class student { public: char name[20]; char sex[10]; float math; float english; float cprogram; void input_name(); void input_sex(); void input_math(); void input_english(); void input_cprogram(); void input(class student* stu); void show_student_massage(class student* stu); }; void student::input_name() { cout << "输入学生姓名: " << endl; cin.getline(name, sizeof(name)); cout << "学生姓名 : " << name << endl; } void student::input_sex() { cout << "输入学生性别: " << endl; cin.getline(sex, sizeof(sex)); } void student::input_math() { cout << "输入学生数学: " << endl; cin >> math; } void student::input_english() { cout << "输入学生英语: " << endl; cin >> english; } void student::input_cprogram() { cout << "输入学生C语言: " << endl; cin >> cprogram; } void student::show_student_massage(class student* stu) { cout << "学生姓名 : " << stu->name << endl; cout << "学生性别 : " << stu->sex << endl; cout << "学生数学 : " << stu->math << endl; cout << "学生英语 : " << stu->english << endl; cout << "学生C语言: " << stu->cprogram << endl; } void student::input(class student* stu) { stu->input_name(); stu->input_sex(); stu->input_math(); stu->input_english(); stu->input_cprogram(); } int main() { student xiaoming; //定义结构体变量 student* xiaoming_point = &xiaoming; //定义指向结构体的指针 xiaoming.input(xiaoming_point); xiaoming.show_student_massage(xiaoming_point); return 0; }
8.3 Cplus/C++ 类访问修饰符
1. 类访问修饰符:
类成员的访问限制使通过在类主题内部对各个区域标记以下关键字来指定的,这些关键字被称为访问修饰符。
class Base { public: // 公有成员 protected: // 受保护成员 private: // 私有成员 };
一个类可以有多个关键词标记区域,每个标记区域在下一个标记区域开始之前,或者遇到类主题结束的右括号之前都是有效的。
成员和类的默认访问修饰符是private。
2. 公有成员:public
公有成员在程序中类的外部可以访问,且可以不使用任何成员函数来设置和获取公有变量的值:
#include <iostream> using namespace std; class Line { public: double length; void setLength(double len); double getLength(void); }; double Line::getLength(void) { return length; } void Line::setLength(double len) { length = len; } int main() { Line line; //设置长度 line.setLength(6.0); cout << "Length of line: " << line.getLength() << endl; //不使用成员函数设置长度时 line.length = 10.0; cout << "Length of line: " << line.getLength() << endl; return 0; }
3. 私有成员(private):
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的,只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。
【实例:我们一般在私有区域定义数据,在共有区域定义相关函数,以便在类的外部也可以调用这些函数。
#include <iostream> using namespace std; class Box { public: double length; void setWidth( double wid ); double getWidth( void ); private: double width; }; // 成员函数定义 double Box::getWidth(void) { return width ; } void Box::setWidth( double wid ) { width = wid; } // 程序的主函数 int main( ) { Box box; // 不使用成员函数设置长度 box.length = 10.0; // OK: 因为 length 是公有的 cout << "Length of box : " << box.length <<endl; // 不使用成员函数设置宽度 // box.width = 10.0; // Error: 因为 width 是私有的 box.setWidth(10.0); // 使用成员函数设置宽度 cout << "Width of box : " << box.getWidth() <<endl; return 0; }
4. 保护成员(protected):
保护成员变量或函数与私有成员十分相似,但有一点不同,保护成员在派生类(即子类)中是可以访问的。
【实例:
#include <iostream> using namespace std; class Box { protected: double width; }; class SmallBox:Box // SmallBox 是派生类 { public: void setSmallWidth( double wid ); double getSmallWidth( void ); }; // 子类的成员函数 double SmallBox::getSmallWidth(void) { return width ; } void SmallBox::setSmallWidth( double wid ) { width = wid; } // 程序的主函数 int main( ) { SmallBox box; // 使用成员函数设置宽度 box.setSmallWidth(5.0); cout << "Width of box : "<< box.getSmallWidth() << endl; return 0; }
5. 继承中的特点:
有三类继承方式,相应地改变了基类成员地访问属性:
- public 继承:基类 public, protected, private 成员的访问属性在派生类中分别变成:public, protected, private
- protected 继承:基类 public, protected, private 成员的访问属性在派生类中分别变成:protected, protected, private
- private 继承:基类 public, protected, private 成员的访问属性在派生类中分别变成:private, private, private
但无论哪种继承方式,都有两点没有改变:
- private 成员只能被本类成员(类内)和友元访问,不能被派生类访问。
- protected 成员可以被派生类访问,但不能在类外访问。
因此可以总结为:
继承方式 | 基类的public成员 | 基类的protected成员 | 基类的private成员 | 继承引起的访问控制关系变化概括 |
---|---|---|---|---|
public继承 | 仍为public成员 | 仍为protected成员 | 不可见 | 基类的非私有成员在子类的访问属性不变 |
protected继承 | 变为protected成员 | 变为protected成员 | 不可见 | 基类的非私有成员都为子类的保护成员 |
private继承 | 变为private成员 | 变为private成员 | 不可见 | 基类中的非私有成员都称为子类的私有成员 |
【实例:public 继承方式
#include <iostream> #include <assert.h> using namespace std; class A { public: int a; A() { a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun() { cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B :public A { public: int a; B(int i) { A(); a = i; } void fun() { cout << a << endl; //正确,public成员 cout << a1 << endl; //正确,基类的public成员,在派生类中仍是public成员。 cout << a2 << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; // 程序的主函数 int main() { B b(10); cout << b.a << endl; cout << b.a1 << endl; //正确 cout << b.a2 << endl; //错误,类外不能访问protected成员 cout << b.a3 << endl; //错误,类外不能访问private成员 system("pause"); return 0; }
【实例:protected继承
#include<iostream> #include<assert.h> using namespace std; class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : protected A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员。 cout << a1 << endl; //正确,基类的public成员,在派生类中变成了protected,可以被派生类访问。 cout << a2 << endl; //正确,基类的protected成员,在派生类中还是protected,可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; //正确。public成员 cout << b.a1 << endl; //错误,protected成员不能在类外访问。 cout << b.a2 << endl; //错误,protected成员不能在类外访问。 cout << b.a3 << endl; //错误,private成员不能在类外访问。 system("pause"); return 0; }
【实例:private 继承
#include<iostream> #include<assert.h> using namespace std; class A{ public: int a; A(){ a1 = 1; a2 = 2; a3 = 3; a = 4; } void fun(){ cout << a << endl; //正确 cout << a1 << endl; //正确 cout << a2 << endl; //正确 cout << a3 << endl; //正确 } public: int a1; protected: int a2; private: int a3; }; class B : private A{ public: int a; B(int i){ A(); a = i; } void fun(){ cout << a << endl; //正确,public成员。 cout << a1 << endl; //正确,基类public成员,在派生类中变成了private,可以被派生类访问。 cout << a2 << endl; //正确,基类的protected成员,在派生类中变成了private,可以被派生类访问。 cout << a3 << endl; //错误,基类的private成员不能被派生类访问。 } }; int main(){ B b(10); cout << b.a << endl; //正确。public成员 cout << b.a1 << endl; //错误,private成员不能在类外访问。 cout << b.a2 << endl; //错误, private成员不能在类外访问。 cout << b.a3 << endl; //错误,private成员不能在类外访问。 system("pause"); return 0; }
8.3 Cplus/C++ 类构造函数 & 析构函数
1. 类的构造函数:
类的一种特殊成员函数,会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回void。
可用于为某些成员变量设置初始值。
【实例:
#include <iostream> using namespace std; class Line { public: void setLength(double len); double getLength(void); Line(); //构造函数 private: double length; }; //成员函数定义 Line::Line(void) { cout << "Object is being created" << endl; } void Line::setLength(double len) { length = len; } double Line::getLength(void) { return length; } int main() { Line line; line.setLength(6.0); cout << "Length of line :" << line.getLength() << endl; return 0; }
执行上述代码得到如下结果:
Object is being created Length of line : 6
2. 带参数的构造函数:
默认的构造函数没有任何参数,但是如果需要也可以添加,这样在创建对象时就会给对象赋初始值。
【实例:
#include <iostream> using namespace std; class Line { public: void setLength(double len); double getLength(void); Line(double len); // 这是构造函数 private: double length; }; // 成员函数定义,包括构造函数 Line::Line(double len) { cout << "Object is being created, length = " << len << endl; length = len; } void Line::setLength(double len) { length = len; } double Line::getLength(void) { return length; } // 程序的主函数 int main() { Line line(10.0); // 获取默认设置的长度 cout << "Length of line : " << line.getLength() << endl; cout << endl; Line line1(0.0); // 再次设置长度 line.setLength(6.0); cout << "Length of line : " << line.getLength() << endl; return 0; }
执行上述代码得到如下结果:
Object is being created, length = 10 Length of line : 10 Object is being created, length = 0 Length of line : 6
3. 使用初始化列表来初始化字段:
Line::Line(double len): length(len) { cout << "Object is being created, length = " << len << endl; }
等同于以下语法:
Line::Line(double len) //类成员声明 { length = len; cout << "Object is being created, length = " << len << endl; }
对于一类C中多个字段的初始化,使用逗号进行分隔:
C::C( double a, double b, double c): X(a), Y(b), Z(c) //初始化列表 { .... }
此外,初始化列表的成员初始化顺序是按照声明顺明,而不是出现在初始化列表中的顺序。
【实例:
#include<iostream> using namespace std; class Student1 { public: int a = 0; int b = 0; void fprint() { cout << " a = " << a << " " << "b = " << b << "\n" << endl; } Student1() { cout << "无参构造函数Student1" << endl; } Student1(int i) :a(i), b(a) { cout << "有参参构造函数Student1" << endl; } Student1(const Student1& t1) { cout << "拷贝构造函数Student1" << endl; this->a = t1.a; this->b = t1.b; } Student1& operator = (const Student1& t1) // 重载赋值运算符 { cout << "赋值函数Student1" << endl; this->a = t1.a; this->b = t1.b; return *this; } }; class Student2 { public: Student1 test; Student2(Student1& t1) { t1.fprint(); cout << "D: "; test = t1; } // Student2(Student1 &t1):test(t1){} }; int main() { cout << "A: "; Student1 A; A.fprint(); cout << "B: "; Student1 B(2); B.fprint(); cout << "C: "; Student1 C(B); C.fprint(); cout << "D: "; Student2 D(C); D.test.fprint(); }
执行上述代码得到如下结果:
A: 无参构造函数Student1 a = 0 b = 0 B: 有参参构造函数Student1 a = 2 b = 2 C: 拷贝构造函数Student1 a = 2 b = 2 D: 无参构造函数Student1 a = 2 b = 2 D: 赋值函数Student1 a = 2 b = 2
4. 类的析构函数:
一类特殊的成员函数,会在每次删除所创建的对象时执行:
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,不会返回任何值,也不能带有任何参数。
析构函数有助于在跳出程序(如关闭文件,释放内存等)前释放资源。
【实例:在上面例子增添析构函数
class Line { public: void setLength(double len); double getLength(void); Line(); // 这是构造函数声明 ~Line(); //这是析构函数声明 private: double length; }; // 成员函数定义,包括构造函数 Line::Line(void) { cout << "Object is being created" << endl; } Line::~Line(void) { cout << "Object is being deleted" << endl; }
执行上述代码得到如下结果:
Object is being created Length of line : 6 Object is being deleted
8.4 Cplus/C++ 拷贝构造函数
1. 定义:
一种特殊的构造函数,具有单个形参(常用 const 修饰),该形参是对该类类型的引用。在创建一个新对象时,是使用同一类之前创建的对象来初始化新创建的对象。
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象,把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个,如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
2. 声明:
classname (const classname &obj) { //构造函数的主体 }
obj 是一个对象引用,该对象是用于初始化另一个对象的。
#include <iostream> using namespace std; class Line { public: int getLength(void); Line(int len); //简单的构造函数 Line(const Line& obj); //拷贝构造函数 ~Line(); //析构函数 private: int* ptr; }; Line::Line(int len) { cout << "调用构造函数" << endl; //为指针分配内存 ptr = new int; *ptr = len; } Line::Line(const Line& obj) { cout << "调用拷贝构造函数并为指针ptr分配内存" << endl; ptr = new int; *ptr = *obj.ptr; //拷贝值 } Line::~Line(void) { cout << "释放内存" << endl; delete ptr; } int Line::getLength(void) { return *ptr; } void display(Line obj) { cout << "Line 大小 :" << obj.getLength() << endl; } int main() { Line line1(10); Line line2 = line1; //调用了拷贝构造函数 display(line1); display(line2); return 0; }
执行上述代码得到如下结果:
调用构造函数 // 创建 line1 ,并调用构造函数 调用拷贝构造函数并为指针ptr分配内存 // 调用 line1 的拷贝构造函数 调用拷贝构造函数并为指针ptr分配内存 // 执行 display(line1),调用其拷贝构造函数 Line 大小 :10 // dispaly 执行 cout 释放内存 // 调用 line1 的析构函数 调用拷贝构造函数并为指针ptr分配内存 // 执行 display(line2),调用 line2 的拷贝构造函数 Line 大小 :10 // display 执行 cout 释放内存 // 调用 line2 的析构函数 释放内存 // 执行 return 0 后,系统自动执行 line1 和 line2 的析构函数 释放内存
3. 拷贝初始化 & 直接初始化:
C++ 支持以下两种初始化形式:拷贝初始化 int a = 5; 和直接初始化 int a(5);
对于其他类型而言没有什么区别,对于 类 类型而言:直接初始化 是调用实参匹配的构造函数;拷贝初始化 是调用拷贝构造函数:
A x(2); //直接初始化,调用构造函数 A y = x; //拷贝初始化,调用拷贝构造函数
4. 必须定义拷贝构造函数的情况:
- 有一个数据成员是指针。
- 有成员表示在构造函数中分配的其他资源。
只包含类 类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可拷贝。
5. 关于为什么当类成员中含有指针类型成员且需要对其分配内存时,一定要有总定义拷贝构造函数 ??
默认的拷贝构造函数实现的只能是浅拷贝,即直接将原对象的数据成员值依次复制给新对象中对应的数据成员,并没有为新对象另外分配内存资源。
这样,如果对象的数据成员是指针,两个指针对象实际上指向的是同一块内存空间。
在某些情况下,浅拷贝回带来数据安全方面的隐患。
当类的数据成员中有指针类型时,我们就必须定义一个特定的拷贝构造函数,该拷贝构造函数不仅可以实现原对象和新对象之间数据成员的拷贝,而且可以为新的对象分配单独的内存资源,这就是深拷贝构造函数。
如何防止默认拷贝发生 ??
声明一个私有的拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类的对象,编译器会报告错误,从而可以避免按值传递或返回对象。
总结:
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
8.5 Cplus/C++ 友元函数
1. 定义:
定义在类的外部,但有权访问类的所有 private 和 protected 成员。
友元可以是一个函数,即友元函数;也可以是一个类,即友元类。在这种情况下,整个类及所有成员都是友元。
2. 声明:
在友元函数的定义中,该函数原型前使用关键字 friend。
class Box { doouble width; public: double length; friend void printWidth(Box box); void setWidth(double wid); }
在友元类的定义中,在类定义中放置如下声明:
friend class ClassTwo;
【实例:
#include <iostream> using namespace std; class Box { double width; public: friend void printWidth( Box box ); void setWidth( double wid ); }; // 成员函数定义 void Box::setWidth( double wid ) { width = wid; } // 请注意:printWidth() 不是任何类的成员函数 void printWidth( Box box ) { /* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */ cout << "Width of box : " << box.width <<endl; } // 程序的主函数 int main( ) { Box box; // 使用成员函数设置宽度 box.setWidth(10.0); // 使用友元函数输出宽度 printWidth( box ); return 0
; }
3. 使用:
友元函数没有 this 指针,则参数有三种情况:
- 要访问非 static 成员时,需要对象做参数;
- 要访问 static 成员或全局变量时,则不需要对象做参数;
- 如果做参数的对象是全局对象,则不需要对象做参数;
【实例:
class INTEGER { friend void Print(const INTEGER& obj); // 声明友元函数 }; void Print(const INTEGER& obj) { // code } void main() { INTEGER obj; Print(obj); // 直接调用 }
8.6 Cplus/C++ 内联函数
1. 定义:
内联函数通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义,如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类定义中定义的函数 都是内联函数,无需 inline 说明符。
【实例:
#include <iostream> using namespace std; inline int Max(int x, int y) { return (x > y)? x : y; } // 程序的主函数 int main( ) { cout << "Max (20,10): " << Max(20,10) << endl; cout << "Max (0,200): " << Max(0,200) << endl; cout << "Max (100,1010): " << Max(100,1010) << endl; return 0; }
执行上述代码得到如下结果:
Max (20,10): 20 Max (0,200): 200 Max (100,1010): 1010
2. 原理上解释:
引入内联函数的目的是为了解决程序中函数调用的效率问题,内联函数时一种性能关键的函数。
程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的节省。所以内联函数一般都是1-5行的小函数,只有当函数小于10行时才会被定义为内联函数。
在使用内联函数时要留神:
- 在内联函数内不允许使用循环语句和开关语句;
- 内联函数的定义必须出现在内联函数第一次调用之前;
- 类结构中所在的类说明内部定义的函数是内联函数。
缺点: 滥用内联将导致程序变慢. 内联可能使目标代码量或增或减, 这取决于内联函数的大小. 内联非常短小的存取函数通常会减少代码大小, 但内联一个相当大的函数将戏剧性的增加代码大小. 现代处理器由于更好的利用了指令缓存, 小巧的代码往往执行更快。
结论: 一个较为合理的经验准则是, 不要内联超过 10 行的函数. 谨慎对待析构函数, 析构函数往往比其表面看起来要更长, 因为有隐含的成员和基类析构函数被调用!
注意:有些函数即使声明为内联的也不一定会被编译器内联, 这点很重要; 比如虚函数和递归函数就不会被正常内联。通常, 递归函数不应该声明成内联函数.(递归调用堆栈的展开并不像循环那么简单, 比如递归层数在编译时可能是未知的, 大多数编译器都不支持内联递归函数)。虚函数内联的主要原因则是想把它的函数体放在类定义内, 为了图个方便, 抑或是当作文档描述其行为, 比如精短的存取函数。
8.6 Cplus/C++ this指针
1. 定义:
每个对象都能通过 this 指针来访问自己的地址,this 指针是所有成员函数的隐含参数,因此在成员函数内部,它可以用来指向调用对象。
只有成员函数才有 this 指针,友元函数没有 this 指针。
#include <iostream> using namespace std; class Box { public: // 构造函数定义 Box(double l=2.0, double b=2.0, double h=2.0) { cout <<"Constructor called." << endl; length = l; breadth = b; height = h; } double Volume() { return length * breadth * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; // Length of a box double breadth; // Breadth of a box double height; // Height of a box }; int main(void) { Box Box1(3.3, 1.2, 1.5); // Declare box1 Box Box2(8.5, 6.0, 2.0); // Declare box2 if(Box1.compare(Box2)) { cout << "Box2 is smaller than Box1" <<endl; } else { cout << "Box2 is equal to or larger than Box1" <<endl; } return 0; }
2. 解说:
当我们调用成员函数时,实际上是替某个对象调用它。
成员函数通过一个名为 this 的额外隐式参数来访问调用它的对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化 this。
例如,如果调用 total.isbn() 则编译器负责把 total 的地址传递给 isbn 的隐式形参 this,可以等价地认为编译器将该调用重写成了以下形式:
//伪代码,用于说明调用成员函数的实际执行过程 Sales_data::isbn(&total)
在成员函数内部,我们可以直接使用 调用该函数的对象 的成员,而无需通过成员访问运算符来进行,因为 this 所指的正是这个对象。任何对类成员的直接访问都被看作时对 this 的隐式引用,即当 isbn 使用 bookNo 时,它隐式地使用 this 指向的成员,就像我们书写了 this->bookNo 。
this 形参时隐式定义的,实际上,任何自定义名为 this 的参数或变量的行为都是非法的。我们可以在成员函数体内部使用 this,因此尽管没有必要,我们还是能把 isbn 定义成如下形式:
std::string isbn() const { return this->bookNo; }
3. this 是一个常量指针,它的目的总是指向 “这个” 对象。
8.7 Cplus/C++ 指向类的指针
1. 定义:
指向C++类的指针与指向结构的指针类似,访问指向类的指针成员时,需要使用成员访问运算符 ->,并且在使用指针前需要进行初始化。
#include <iostream> using namespace std; class Box { public: //构造函数定义 Box(double l = 2.0, double b = 2.0, double h = 2.0) { cout << "Constrcuctor called." << endl; length = l; breadth = b; height = h; } double Volume() { return length * breadth * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; double breadth; double height; }; int main(void) { Box Box1(3.3, 1.2, 1.5); Box Box2(8.5, 6.0, 2.0); Box* ptrbox = 0; //declare pointer to a class. //保存指针地址 ptrbox = &Box1; //使用成员访问运算符来访问成员 cout << "Volume of Box1: " << ptrbox->Volume() << endl; ptrbox = &Box2; cout << "Volume of Box2: " << ptrbox->Volume() << endl; return 0; }
执行上述代码得到如下结果
Constructor called. Constructor called. Volume of Box1: 5.94 Volume of Box2: 102
8.8 Cplus/C++ 类的静态成员
1. 定义:
使用 static 关键字将类成员定义为静态的。当我们声明类成员为静态时,意味着无论创建多少个类的对象,静态成员都只有一个副本。
静态成员变量在类中仅仅是声明,没有定义,需要在类外进行定义和初始化(给静态成员变量分配内存,并赋予初始值)。
静态成员在类的所有对象中是共享的。
2. 初始化:
如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为 0.
静态成员的初始化不能放置在类的定义中,但是可以在类的外部,通过使用 范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
【实例:
#include <iostream> using namespace std; class Box { public: static int objectCount; //构造函数定义 Box(double l = 2.0, double b = 2.0, double h = 2.0) { cout << "Constrcuctor called." << endl; length = l; breadth = b; height = h; objectCount++; } double Volume() { return length * breadth * height; } int compare(Box box) { return this->Volume() > box.Volume(); } private: double length; double breadth; double height; }; //初始化类 Box 的静态成员 int Box::objectCount = 0; int main(void) { Box Box1(3.3, 1.2, 1.5); cout << "Box1 object: " << Box1.objectCount << endl; Box Box2(8.5, 6.0, 2.0); cout << "Box1 object: " << Box1.objectCount << endl; cout << "Box2 object: " << Box2.objectCount << endl; cout << "Total objects: " << Box::objectCount << endl; return 0; }
执行上述代码得到如下结果:
Constrcuctor called. Box1 object: 1 Constrcuctor called. Box1 object: 2 Box2 object: 2 Total objects: 2
3. 类中特殊成员变量的初始化问题:
- 常量变量:必须通过构造函数参数列表进行初始化。
- 引用变量:必须通过构造函数参数列表进行初始化。
- 普通静态变量:要在类外通过"::"初始化。
- 静态整型常量:可以直接在定义的时候初始化。
- 静态非整型常量:不能直接在定义的时候初始化。要在类外通过"::"初始化。
4. 静态成员函数:
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。
静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数有一个类范围,它们不能访问类的 this 指针,您可以使用静态成员函数来判断类的某些对象是都已经被创建。
静态成员函数与普通成员函数的区别:
-
- 静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)和类外部的其他函数。
- 普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
【实例:
#include <iostream> using namespace std; class Box { public: static int objectCount; //构造函数定义 Box(double l = 2.0, double b = 2.0, double h = 2.0) { cout << "Constrcuctor called." << endl; length = l; breadth = b; height = h; objectCount++; } double Volume() { return length * breadth * height; } // 创建静态成员函数 static int getCount() { return objectCount; } private: double length; double breadth; double height; }; //初始化类 Box 的静态成员 int Box::objectCount = 0; int main(void) { //在创建对象前输出对象总数 cout << "Initial Stage Count: " << Box::getCount() << endl; Box Box1(3.3, 1.2, 1.5); Box Box2(8.5, 6.0, 2.0); //在创建对象后输出对象总数 cout << "Final Stage Count: " << Box::getCount() << endl; return 0; }
执行上述代码得到如下结果:
Initial Stage Count: 0 Constrcuctor called. Constrcuctor called. Final Stage Count: 2
5. 利用静态成员变量分析构造和析构函数的调用情况。
【实例:
#include <iostream> using namespace std; class Cpoint { public: static int value; static int num; //Cpoint 构造函数 Cpoint(int x, int y) { xp = x; yp = y; value++; cout << "调用构造:" << value << endl; cout << this->xp << " " << this->yp << endl; } //Cponit 析构函数 ~Cpoint() { num++; cout << "调用析构:" << num << endl; cout << this->xp << " " << this->yp << endl; } private: int xp, yp; }; //初始化类 Cpoint 的静态成员变量 int Cpoint::value = 0; int Cpoint::num = 0; class CRect { public: CRect(int x1, int x2) :mpt1(x1, x1), mpt2(x2, x2) { cout << "调用构造\n"; } ~CRect() { cout << "调用析构\n"; } private: Cpoint mpt1, mpt2; }; int main() { CRect p(10, 20); cout << "Hello, world!" << endl; return 0; }
执行上述代码得到如下结果:
调用构造:1 10 10 调用构造:2 20 20 调用构造 Hello, world! 调用析构 调用析构:1 20 20 调用析构:2 10 10
若改变 CRect 中调用另一个类的声明顺序如下:
CRect(int x1, int x2) :mpt2(x2, x2), mpt1(x1, x1) { cout << "调用构造\n"; }
执行代码得到的结果仍相同。