听课笔记---程序设计与算法(三)C++面向对象程序设计(郭伟老师)---第二周
Week 2
Contents
成员与隐藏
构造函数
析构函数
Thoughts
成员与隐藏
成员函数的定义与调用
- 成员函数可定义在类内部,与普通函数类似,也可定义在类外部,但其声明总是在类内部,形式如下
class A {
int Fun();
}
int A::Fun()
{
...
}
- 成员函数必须由对象或指向对象的指针调用
private
and public
- 关键字可以在定义时反复使用,不限顺序
class A {
public:
...
private:
...
public:
...
}
-
没有关键字的视为
private
class A { int a; // private int Fun(); //private public: int b; // public };
private
成员只能在成员函数内部访问,public
成员可以在任何地方访问
成员函数的可访问范围
- 当前对象的全部属性,函数
- 同类对象所有属性及函数(以函数参数传递而来)
其余地带可访问范围
对象的公共成员
“隐藏”机制及其作用
- 设置私有成员的机制为”隐藏“
- 强制对成员的访问一定要通过成员函数,以后对成员的属性(如数据类型,返回值类型)进行改变后,只需要改变对应的成员函数中的操作即可,不必修改其余地带直接使用该成员的语句
隐藏机制实例
#include<iostream>
#include<cstring>
class CEmployee {
private:
char szName[30];
int getSalary();
public:
int salary;
void setName(const char * name);
void getName(char * name);
void averageSalary(CEmployee e1, CEmployee e2);
};
void CEmployee::setName(const char * name)
{
strcpy(szName, name); //直接使用私有成员szName,合法
}
void CEmployee::getName(char * name)
{
strcpy(name, szName);//直接使用私有成员szName,合法
}
int CEmployee::getSalary()
{
return salary;
}
int main()
{
CEmployee A;
char name[30];
A.setName("Tom"); // 合法
A.getSalary(); // 非法,直接在其他地带调用私有函数
strcpy(name, A.szName); // 非法,直接使用私有变量
return 0;
}
成员函数的重载及参数缺省
成员函数也可以重载和参数缺省
但是要避免二义性错误, 如
class A {
void Fun();
void Fun(int a = 0);
}
int main()
{
Fun();// ambiguous
}
构造函数
构造函数基本概念
- 成员函数的一种
- 名字与类名相同,可以有参数,不能有返回值(
void
)也不行 - 负责初始化,不负责分配空间
- 程序自动生成无参数构造函数,无任何操作。在程序员自己定义了构造函数以后,缺省的构造函数失效,若仍需无参数构造函数需自己写
- 一个类可以有多个构造函数,各函数之间为重载关系
- 对象生成时,构造函数会被调用(无论什么形式生成),构造函数执行且仅执行一次
- 不允许出现
A::A(A)
式的构造函数
构造函数的意义
- 自动初始化对象,不再需要额外初始化对象
- 避免无初始化导致的未知错误
允许private
构造函数
private
构造函数不能用来直接初始化对象
构造函数使用实例
构造函数基本使用
#include<iostream>
class Complex {
double real, imag; // private
public:
Complex(double real, double imag);
Complex(double i);
Complex(Complex, Complex);
};
Complex::Complex(double r, double i)
{
real = r;
imag = i;
}
Complex::Complex(double i)
{
real = i;
imag = 0;
}
Complex::Complex(Complex c1, Complex c2)
{
real = c1.real + c2.real;
imag = c1.imag + c2.imag; // 均合法,使用同类对象私有变量
}
int main()
{
Complex a; // 非法,无参缺省构造函数已停用
Complex c1(4), c2(2, 1); // 自动转化为 double
Complex c(c1, c2);
Complex * p = new Complex(1);
return 0;
}
构造函数与数组
#include<iostream>
class Test {
public:
int x;
Test()
{
std::cout << "Constructor 1 Called." << std::endl;
}
Test(int i)
{
x = i;
std::cout << "Constructor 2 Called." << std::endl;
}
Test(int a, int b)
{
x = a + b;
std::cout << "Constructor 3 Called." << std::endl;
}
};
int main()
{
Test A[2] = {2}; // A[1]只有一个空元素也要调用constructor
std::cout << "Step 1" << std::endl;
Test B[2] = {Test(2, 1), 5};
std::cout << "Step 2" << std::endl;
Test * P = new Test[2]{1, Test(2,1)}; // 这种初始化方式C++11才有,warning: 额外的初始化,new feature
std::cout << "Step 3" << std::endl;
delete []P;
Test * PA[3] = {new Test(1), new Test(1,2), NULL}; // PA[2]只有一个指针不调用constructor
delete PA[1];
delete PA[0];
std::cout << "Step 4" << std::endl;
return 0;
}
输出
Constructor 2 Called.
Constructor 1 Called.
Step 1
Constructor 3 Called.
Constructor 2 Called.
Step 2
Constructor 2 Called.
Constructor 3 Called.
Step 3
Constructor 2 Called.
Constructor 3 Called.
Step 4
复制构造函数
复制构造函数基本概念
- 构造函数中的一种
- 只有一个参数,即对同类对象的引用(可以是
const
或非const
,一般使用const
) - 编译器会自动生成复制构造函数,若程序员自己定义,则自动生成的复制构造函数不存在
复制构造函数实例
自动生成的复制构造函数
#include<iostream>
class A {
public:
int v;
A(int i)
{
v = i;
}
};
int main()
{
A c1(5);
A c2(c1); // 缺省的复制构造函数被调用
std::cout << c1.v << std::endl;
std::cout << c2.v << std::endl;
return 0;
}
输出
5
5
三种复制构造函数起作用的情况
-
用一个对象初始化另一个同类的对象
#include<iostream> class A { public: int v; A(int i) { v = i; } A(A & a) { v = a.v; std::cout << "Copy constructor called." << std::endl; } }; int main() { A c1(5); A c2(c1); // 初始化,复制构造函数被调用 A c3 = c2; // 同样是初始化语句,而非赋值语句,复制构造函数被调用 c1 = c2; // 赋值语句,不调用复制构造函数 return 0; }
Output
Copy constructor called.
Copy constructor called. -
函数参数为类的对象,形参会被复制构造函数初始化
#include<iostream> class A { public: int v; A(int i) { v = i; } A(A & a) { v = a.v; std::cout << "Copy constructor called." << std::endl; } }; void Fun(A a) { std::cout << a.v << std::endl; } int main() { A c1(5); Fun(c1); // 复制构造函数被调用形成形参 return 0; }
Output
Copy constructor called.
5 -
函数返回值为类的对象,返回时,复制构造函数初始化返回值
#include<iostream> class A { public: int v; A(int i) { v = i; } A(A & a) { v = a.v; std::cout << "Copy constructor called." << std::endl; } }; A Fun(A a) { std::cout << a.v << std::endl; return a; } int main() { A c1(5); std::cout << Fun(c1).v << std::endl; // 复制构造函数被调用形成形参,再被调用形成返回值 return 0; }
Output
Copy constructor called.
5
Copy constructor called.
5
Tips
- 赋值语句不调用复制构造函数
- 使用常量引用参数,避免复制构造函数的调用开销,并避免在函数体内改变被引用的对象
类型转换构造函数
类型转换构造函数基本概念
- 实现类型的自动转换为目的
- 只有一个参数,不是复制构造函数(即该参数非
& A
类型),即可称为类型转换构造函数 - 构造一个临时变量
类型转换构造函数实例
#include<iostream>
class Complex {
public:
double real, imag;
Complex(int i) // convert int into Complex
{
std::cout << "IntConstructor Called." << std::endl;
real = i;
imag = 0;
}
Complex(double r, double i)
{
real = r;
imag = i;
}
};
int main()
{
Complex aa(4,5);
Complex a(4); // 类型转换构造函数被调用,用于初始化a,不产生临时变量
Complex aaa = 4; // 同样用于初始化,不产生临时变量
aaa = 9; // 类型转换构造函数被调用,先生成Complex(9)初始化的临时变量,再赋给aaa
return 0;
}
Output:
IntConstructor Called.
IntConstructor Called.
IntConstructor Called.
析构函数
析构函数基本概念
- 名字与类名相同,在类名前加
~
符号 - 无参数和返回值
- 一个类最多拥有一个析构函数
- 析构函数在对象消亡时被调用
- 缺省的析构函数几乎不进行操作,程序员自定义后缺省的析构函数便消失
析构函数与数组,指针
对象数组消亡时,每个对象的析构函数都会被调用
指针所指的动态分配的对象,不delete就不会消亡,也不调用析构函数
析构函数与函数返回值
对象在以返回值返回时,先调用赋值构造函数生成临时变量,而后消亡调用析构函数,随后临时变量也会消亡,也调用析构函数。
析构函数实例
析构函数与数组
#include<iostream>
class A {
public:
~A()
{
std::cout << "Destructor Called." << std::endl;
}
};
int main()
{
A Test[2]; // 两个对象,消亡时调用两次析构函数
A * P = new A[3]; //三个对象, 不delete就不调用析构函数
return 0;
}
Output:
Destructor Called.
Destructor Called.
析构函数与函数调用
#include<iostream>
class A {
public:
int x;
A(int i)
{
x = i;
}
A(A & a)
{
x = a.x;
std::cout << "Copy called" << std::endl;
}
~A()
{
std::cout << x << "Destructor Called" << std::endl;
}
};
A Fun()
{
A b(4);
return b;
}
A a(5);
int main()
{
a = Fun(); //
std::cout << "main ends" << std::endl;
return 0;
}
Output:
Copy called
4Destructor Called //b
4Destructor Called //临时变量
main ends4Destructor Called //a
*编译器中的优化
在许多编译器中,函数的类的对象类型的返回值不会调用复制构造函数生成临时变量,也不会调用析构函数,比如VS在Debug 和 Release 两种模式下,表现是不一样的。