感谢【北京大学】程序设计实习 (C++版) 郭炜/刘家瑛老师老师的辛勤付出,花了一段时间整理课的内容
目录
第3周 类和对象进阶
3-1复制构造函数
基本概念:
- 只有一个参数,即对同类对象的引用。
- 形如 X::X( X& )或X::X(const X &), 二者选一, 后者能以常量对象作为参数
- 如果没有定义复制构造函数,那么编译器生成默认复制构造函数。默认的复制构造函数完成复制功能。
程序示例:
class Complex {
private :
double real,imag;
};
Complex c1; //调用缺省无参构造函数
Complex c2(c1);//调用缺省的复制构造函数,将 c2 初始化成和c1一样
程序示例:
如果定义的自己的复制构造函数,则默认的复制构造函数不存在。
class Complex {
public :
double real,imag;
Complex(){ }
Complex( const Complex & c ) {
real = c.real;
imag = c.imag;
cout << “Copy Constructor called”;
}
};
Complex c1;
Complex c2(c1);//调用自己定义的复制构造函数,输出 Copy Constructor called
不允许有形如 X::X( X )的构造函数。
class CSample {
CSample( CSample c ) {
} //错,不允许这样的构造函数
}
复制构造函数起作用的三种情况
1)当用一个对象去初始化同类的另一个对象时。
Complex c2(c1);
Complex c2 = c1; //初始化语句,非赋值语句
2)如果某函数有一个参数是类 A 的对象,那么该函数被调用时,类A的复制构造函数将被调用。
class A
{
public:
A() { };
A( A & a) {
cout << "Copy constructor called" <<endl;
}
};
void Func(A a1)
{ }
int main(){
A a2;
Func(a2);
return 0;
}
程序输出结果为: Copy constructor called
3) 如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
class A
{
public:
int v;
A(int n) { v = n; };
A( const A & a) {
v = a.v;
cout << "Copy constructor called" <<endl;
}
};
A Func() {
A b(4);
return b;
}
int main() {
cout << Func().v << endl;
return 0;
}
输出结果:
Copy constructor called
4
程序示例:
#include <iostream>
using namespace std;
class A {
public:
int v;
A(){
}
A(int n) {
v = n;
}
A(const A& a){
v = a.v;
cout << "复制构造函数被调用" << endl;
}
};
A Func1() {
A b(4);
cout << b.v << endl;
return b;
}
void Func2(A a1) { }
int main() {
cout << Func1().v << endl;
A a2;
Func2(a2);
return 0;
}
执行结果:
3-2类型转化函数
目的
• 实现类型的自动转换
特点
• 只有一个参数
• 不是复制构造函数
编译系统会自动调用 à 转换构造函数
à 建立一个 临时对象 / 临时变量
程序示例:
#include <iostream>
using namespace std;
class Complex {
public:
double real, imag;
Complex(int i) { //类型转换构造函数
cout << "IntConstructor called" << endl;
real = i; imag = 0;
}
Complex(double r, double i)
{
real = r; imag = i;
}
};
int main() {
Complex c1(7, 8);
Complex c2 = 12;
c1 = 9; // 9被自动转换成一个临时Complex对象
cout << c1.real << "," << c1.imag << endl;
return 0;
}
运行结果为:
3-3 析构函数
析构函数 (Destructor)
-
成员函数的一种
- 名字与类名相同
- 在前面加 ‘~’
- 没有参数和返回值
- 一个类最多只有一个析构函数
- 对象消亡时 自动被调用
在对象消亡前做善后工作,释放分配的空间等
- 定义类时没写析构函数, 则编译器生成缺省析构函数
不涉及释放用户申请的内存释放等清理工作
- 定义了析构函数, 则编译器不生成缺省析构函数
备忘:
程序示例:
class String{
private :
char * p;
public:
String () {
p = new char[10];
}
~ String ();
};
String ::~ String() {
delete [] p;
}
程序示例:
析构函数和数组,对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用
class Ctest {
public:
~Ctest() { cout<< "destructor called" << endl; }
};
int main () {
Ctest array[2];
cout << "End Main" << endl;
return 0;
}
执行:
析构函数和运算符 delete:
delete 运算导致析构函数调用
Ctest* pTest;
pTest = new Ctest; //构造函数调用
delete pTest; //析构函数调用
------------------------------------------------------------------
pTest = new Ctest[3]; //构造函数调用3次
delete [] pTest; //析构函数调用3次
构造函数和析构函数调用时机的例题
class Demo {
int id;
public:
Demo( int i ){
id = i;
cout << “id=” << id << “ Constructed” << endl;
}
~Demo(){
cout << “id=” << id << “ Destructed” << endl;
}
};
Demo d1(1);
void Func(){
static Demo d2(2);
Demo d3(3);
cout << “Func” << endl;
}
int main (){
Demo d4(4);
d4 = 6;
cout << “main” << endl;
{
Demo d5(5);
}
Func();
cout << “main ends” << endl;
return 0;
}
运行结果为:
程序示例:
#include <iostream>
using namespace std;
class String {
private:
char * p;
public:
int b;
String() {
b = 5;
p = new char[10];
}
~String();
};
String ::~String() {
delete[] p;
}
int main() {
String* a2 = new String;;
delete a2;
cout << a2->b << endl;
return 0;
}
运行结果:
构造函数和析构函数在不同编译器中的表现
- 各别调用情况不一致
- 编译器有bug
- 代码优化措施
- 前面讨论的是C++标准
3-4 静态成员函数和静态成员变量
静态成员基本概念:
-
静态成员:在说明前面加了static关键字的成员。
class CRectangle
{
private:
int w, h;
static int nTotalArea; //静态成员变量
static int nTotalNumber;
public:
CRectangle(int w_,int h_);
~CRectangle();
static void PrintTotal(); //静态成员函数
};
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享。
sizeof 运算符不会计算静态成员变量。
class CMyclass {
int n;
static int s;
};
则 sizeof( CMyclass ) 等于 4
- 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份, 为所有对象共享。
- 普通成员函数必须具体作用于某个对象,而静态成员函数并不具体作用与某个对象。
- 因此静态成员不需要通过对象就能访问。
如何访问静态成员:
1) 类名::成员名
CRectangle::PrintTotal();
2) 对象名.成员名
CRectangle r; r.PrintTotal();
3) 指针->成员名
CRectangle * p = &r; p->PrintTotal();
4) 引用.成员名
CRectangle & ref = r; int n = ref.nTotalNumber;
- 静态成员变量本质上是全局变量,哪怕一个对象都不存在,类的静态成员变量也存在。
- 静态成员函数本质上是全局函数。
- 设置静态成员这种机制的目的是将和某些类紧密相关的全局变量和函数写到类里面,看上去像一个整体,易于维护和理解。
- 注意事项
在静态成员函数中,不能访问非静态成员变量,也不能调用非静态成员函数
3-5 成员对象和封闭类
成员对象和封闭类
成员对象: 一个类的成员变量是另一个类的对象
包含成员对象的类叫封闭类 (Enclosing)
class CTyre { //轮胎类
private:
int radius; //半径
int width; //宽度
public:
CTyre(int r, int w):radius(r), width(w) { }
};
class CEngine { //引擎类
};
class CCar { //汽车类 “封闭类”
private:
int price; //价格
CTyre tyre;
CEngine engine;
public:
CCar(int p, int tr, int tw);
};
CCar::CCar(int p, int tr, int w):price(p), tyre(tr, w){
};
int main(){
CCar car(20000,17,225);
return 0;
}
如果 CCar 类不定义构造函数, 则CCar car; // error 编译出错
编译器不知道 car.tyre 该如何初始化
car.engine 的初始化没有问题: 用默认构造函数
生成封闭类对象的语句 明确 “对象中的成员对象”如何初始化
封闭类构造函数的初始化列表:
定义封闭类的构造函数时, 添加初始化列表:
类名::构造函数(参数表):成员变量1(参数表), 成员变量2(参数表), …
{ … }
成员对象初始化列表中的参数
• 任意复杂的表达式
• 函数 / 变量/ 表达式中的函数, 变量有定义
调用顺序:
当封闭类对象生成时,
• S1: 执行所有成员对象的构造函数
• S2: 执行封闭类的构造函数
成员对象的构造函数调用顺序
• 和成员对象在类中的说明顺序一致
• 与在成员初始化列表中出现的顺序无关
当封闭类的对象消亡时,
• S1: 先执行 封闭类 的析构函数
• S2: 执行 成员对象 的析构函数
析构函数顺序和构造函数的调用顺序相反
程序示例:
class CTyre {
public:
CTyre() { cout << "CTyre contructor" << endl; }
~CTyre() { cout << "CTyre destructor" << endl; }
};
class CEngine {
public:
CEngine() { cout << "CEngine contructor" << endl; }
~CEngine() { cout << "CEngine destructor" << endl; }
};
class CCar {
private:
CEngine engine;
CTyre tyre;
public:
CCar( ) { cout << “CCar contructor” << endl; }
~CCar() { cout << "CCar destructor" << endl; }
};
int main(){
CCar car;
return 0;
}
程序的输出结果是:
CEngine contructor
CTyre contructor
CCar contructor
CCar destructor
CTyre destructor
CEngine destructo
3-6 友元
友元分为:
友元函数,一个类的友元函数可以访问该类的私有成员
友元类,A是B的友元类 A的成员函数可以访问B的私有成员
注意事项:
友元类之间的关系
不能传递, 不能继承
友元详情见博文:https://www.cnblogs.com/Libinkai/p/10622473.html
3-7 this指针
this指针作用:其作用就是指向成员函数所作用的对象
非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针
程序示例:
class Complex {
public:
double real, imag;
void Print() { cout << real << "," << imag ; }
Complex(double r,double i):real(r),imag(i)
{ }
Complex AddOne() {
this->real ++; //等价于 real ++;
this->Print(); //等价于 Print
return * this;
}
};
int main() {
Complex c1(1,1),c2(0,0);
c2 = c1.AddOne();
return 0;
} //输出 2,
程序示例2:
#include <iostream>
using namespace std;
class A
{
int i;
public:
void Hello() {
cout << "hello" << endl;
}
/*void Hello(A * this){
cout << this->i << "hello"
<< endl;
}*/
};
//this若为NULL,则出错!!
int main()
{
A * p = NULL;
p->Hello();
//Hello(p);
return 0;
} // 输出: hello
输出结果为:
this指针和静态成员函数:
静态成员函数中不能使用 this 指针!因为静态成员函数并不具体作用与某个对象!
因此,静态成员函数的真实的参数的个数,就是程序中写出的参数个数!
3-8 常量对象,常量成员变量,常引用
-
常量对象
如果不希望某个对象的值被改变,则定义该对象的时候可以在前面加const关键字。
程序示例:
class Demo{
private :
int value;
public:
void SetValue() { }
};
const Demo Obj; // 常量对象
- 常量成员函数:
在类的成员函数说明后面可以加const关键字,则该成员函数成为常量成员函数。常量成员函数执行期间不应修改其所作用的对象。因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外),也不能调用同类的非常量成员函数(静态成员函数除外)。
常量成员函数
程序示例:
class Sample
{
public:
int value;
void GetValue() const;
void func() { };
Sample() { }
};
void Sample::GetValue() const
{
value = 0; // wrong
func(); //wrong
}
int main() {
const Sample o;
o.value = 100; //err.常量对象不可被修改
o.func(); //err.常量对象上面不能执行非常量成员函数
o.GetValue(); //ok,常量对象上可以执行常量成员函数
return 0;
} //在Dev C++中,要为Sample类编写无参构造函数才可以, Visual Studio2010中不需要
常量成员函数的重载
两个成员函数,名字和参数表都一样,但是一个是const,一个不是,算重载。
程序示例:
#include <iostream>
using namespace std;
class CTest {
private:
int n;
public:
CTest() { n = 1; }
int GetValue() const { return n; }
int GetValue() { return 2 * n; }
};
int main() {
const CTest objTest1;
CTest objTest2;
cout << objTest1.GetValue() << "," << objTest2.GetValue();
return 0;
}
运行结果为:
常引用
引用前面可以加const关键字,成为常引用,不能通过常引用,修改其引用的变量。
const int & r = n;
r = 5; //error
n = 4; //ok
对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。用指针作参数,代码又不好看,对象引用作为函数的参数有一定风险性,若函数 中不小心修改了形参o,则实参也跟着变,这可能不是我们想要的。
可以用对象的常引用作为参数,如:
class Sample {
…
};
void PrintfObj( const Sample & o)
{
……
}
这样函数中就能确保不会出现无意中更改o值的语句了。