C++面向对象程序设计笔记:第二章 类和对象基础
类和对象的基本概念
在类的定义中,用下列访问范围关键字来说明类成员可被访问的范围:
- private: 私有成员,只能在成员函数内访问
- public : 公有成员,可以在任何地方访问
- protected: 保护成员
注:这三种关键字出现的次数和先后次序没有限制。
如果某个成员前面没有上述关键字,则缺省地被认为是私有成员。
构造函数
成员函数的一种:
- 名字与类名相同,可以有参数,不能有返回值(void也不行)
- 作用是对对象进行初始化,如给成员变量赋初值
- 如果定义类时没写构造函数,则编译器生成一个默认的无参数的构造函数(不做任何操作)。
- 如果定义了构造函数,则编译器不生成默认的无参数的构造函数
- 对象生成时构造函数自动被调用。对象一旦生成,就再也不能在其上执行构造函数
- 一个类可以有多个构造函数,参数个数或类型不同
无构造函数:
class Complex {
private:
double real, imag;
public:
void Set(double r, double i);
}; // 编译器自动生成默认构造函数
Complex c1; // 默认构造函数被调用
Complex* pc = new Complex; // 默认构造函数被调用
有构造函数:
class Complex {
private:
double real, imag;
public:
Comple(double r, double i=0);
};
Complex::Complex(double r, double i) {
read = r, imag = i;
}
Complex c1; // 编译错误,缺少构造函数的参数
Complex* pc = new Complex; // 编译错误,同上
Complex c1(2); // ok
构造函数在数组中的使用:
class CSample {
int x;
public:
CSample() {
cout << "Constructor 1 Called" << endl;
}
CSample(int n) {
x = n
cout << "Constructor 2 Called" << endl;
}
};
int main() {
CSample array1[2]; // 无参构造Con1
CSample array2[2] = {4, 5}; // 有参构造函数Con2
CSample array3[2] = {3}; // 第一个有参Con2,第二个无参Con1
CSample* array4 = new CSample[2]; // 无参Con1
delete []array4;
return 0;
}
class Test {
public:
Test(int n){} // (1)
Test(int n, int m){} // (2)
Test(){} // (3)
}
Test array1[3] = {1, Test(1, 2)}; // (1),(2),(3)
Test array2[3] = {Test(2, 3), Test(1, 2), 1}; // (2),(2),(1)
Test* pArray[3] = {new Test(4), new Test(1,2)}; // (1),(2),NULL
复制构造函数
只有一个参数,即对同类对象的引用。编译器会自动实现一个默认的。
X::X(X& x); X::X(const X& x);
class Complex {
private:
double real, imag;
/*
Comple(const Complex& c) {
// ...
}
*/
}
Complex c1; // 调用缺省无参构造函数
Complex c2(c1); // **调用缺省的复制构造函数**,将c2初始化成和c1一样。
使用的三种情况:
当用一个对象去初始化同类的另一个对象时
Complex c2(c1); Complex c2 = c1; // **初始化语句**,非赋值语句
如果某函数有一个参数是类A的对象,那么该函数被调用时,类A的复制构造函数被调用。
void Func(A a1){ } int main() { A a2; Func(a2); // 会调用A的复制构造函数 return 0; }
如果函数的返回值是类A的对象时,则函数返回时,A的复制构造函数被调用:
A Func() { A b(4); return b; // 调用复制构造函数 } int main() { cout << Func().c; return 0; }
void fun(CMyclass obj_) { }
对于这样的函数,调用时生成形参会引发复制构造函数调用,开销比较大。
可以考虑用CMyclass& 引用类型作为参数。
如果希望确保实参的值在函数中不应该改变,那么可以加上const关键字。
对象间的赋值并不导致复制构造函数被调用
注意初始化和复制构造函数的区别!
class CMyclass {
public:
int n;
CMyclass() {}
CMyclass(CMyclass& c) { n = 2 * c.n; }
}
int main() {
CMyclass c1, c2;
c1.n = 5;
c2 = c1; // 赋值,不调用
CMyclass c3(c1);// 初始化,调用复制构造函数
// c2.n == 5
// c3.n == 10
}
类型转换构造函数
实现类型的自动转换。
只有一个参数,而且不是复制构造函数的构造函数,一般就可以看作是转换构造函数。
当需要的时候,编译系统会自动调用转换构造函数,建立一个无名的临时对象(或临时变量)。
class Complex {
public:
double real, imag;
Complex(int i) { // 类型转换构造函数
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对象
return 0;
}
析构函数
- 名字与类名相同,在前面加
~
,没有参数和返回值,一个类最多只能有一个析构函数。 - 析构函数在对象消亡时被自动调用。可以定义析构函数来做对象消亡前的善后工作,比如释放分配的空间等。
- 如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。
- 如果定义了析构函数,则编译器不生成缺省析构函数。
对象数组生命周期结束时,对象数组的每个元素的析构函数都会被调用。
CMyclass obj;
CMyclass fun(CMyclass sobj) { // 参数对象消亡也会导致析构函数被调用
return sobj; // 函数调用返回时生成临时对象返回
}
int main() {
obj = fun(obj); // 函数调用的返回值被用过后,该临时对象析构函数被调用
return 0; // 程序结束后,obj的析构函数被调用
}
// 总共调用了3次析构函数
Demo:
class Demo {
int id;
public:
Demo(int i) {
id = i;
cout << "id=" << id << " constructed\n";
}
~Demo() {
cout << "id=" << id << " constructed\n";
}
};
Demo d1(1);
void Func() {
static Demo d2(2);
Demo d3(3);
cout << "func\n";
}
int main() {
Demo d4(4);
d4 = 6;
cout << "main\n";
{
Demo d5(5);
}
Func();
cout << "main ends\n";
return 0;
}
/*
输出:
id=1 constructed
id=4 constructed
id=6 constructed
id=6 constructed
main
id=5 constructed
id=5 constructed
id=2 constructed
id=3 constructed
func
id=3 constructed
main ends
id=6 constructed // d4调用析构函数
id=2 constructed
id=1 constructed // 先初始化的后析构
*/
错题:
如下程序调用析构函数几次?
3次,p2不会消亡,因为指针只有在delete的时候才会消亡。
int main() {
A* p = new A[2];
A *p2 = new A;
A a;
delete[] p;
}
注:dev会出于优化目的,不生成返回值临时对象。
课后习题
005:编程填空:学生信息处理程序
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <sstream>
#include <cstdlib>
using namespace std;
class Student {
private:
char *name;
int age, number;
int socre1, socre2, socre3, socre4;
float res;
public:
Student() {
name = new char[20];
}
void input() {
scanf("%[^,],%d,%d,%d,%d,%d,%d", name, &age, &number, &socre1, &socre2, &socre3,&socre4);
}
void calculate() {
res = 1.0 * (socre1 + socre2 + socre3 + socre4) / 4.0;
}
void output() {
if(int(res * 10) % 10 == 0)
printf("%s,%d,%d,%.0f", name, age, number, res);
else
printf("%s,%d,%d,%.1f", name, age, number, res);
}
};
int main() {
Student student; // 定义类的对象
student.input(); // 输入数据
student.calculate(); // 计算平均成绩
student.output(); // 输出数据
}
006:奇怪的类复制
#include <iostream>
using namespace std;
class Sample {
public:
int v;
Sample(int v_ = 0) {
v = v_;
}
Sample(const Sample& a) {
v = a.v + 2;
}
void operator=(Sample& a) {
v = 5;
}
};
void PrintAndDouble(Sample o)
{
cout << o.v;
cout << endl;
}
int main()
{
Sample a(5);
Sample b = a;
PrintAndDouble(b);
Sample c = 20;
PrintAndDouble(c);
Sample d;
d = a;
cout << d.v;
return 0;
}
008:超简单的复数类
#include <iostream>
#include <cstring>
#include <cstdlib>
using namespace std;
class Complex {
private:
double r,i;
public:
void Print() {
cout << r << "+" << i << "i" << endl;
}
Complex() {
r = 0;
i = 0;
}
Complex(char *s) {
r = i = 0;
int n = strlen(s);
int pos;
for(pos = 0; pos < n; pos++) {
if(s[pos] == '+') {
break;
}
r *= 10;
r += s[pos] - '0';
}
pos++;
for(; pos < n - 1; pos++) {
i *= 10;
i += s[pos] - '0';
}
}
};
int main() {
Complex a;
a = "3+4i"; a.Print();
a = "5+6i"; a.Print();
return 0;
}
009:哪来的输出
#include <iostream>
using namespace std;
class A {
public:
int i;
A(int x) { i = x; }
~A() {
cout << i << "\n";
}
};
int main()
{
A a(1);
A * pa = new A(2);
delete pa;
return 0;
}