C++面向对象程序设计-北京大学-郭炜【课程笔记(二)】
课程链接:程序设计与算法(三)C++面向对象程序设计 北京大学 郭炜
课程PPT:github提供的对应课程PPT
1、结构化程序设计
C语言使用结构化程序设计:
程序 = 数据结构 + 算法
- 程序由全局变量以及众多相互调用的函数组成。
- 算法以函数的形式实现,用于对数据结构进行操作。
结构化程序设计的不足
- 结构化程序设计中,函数和其所操作的数据结构,没有直观的联系。
- 随着程序规模的增加,程序逐渐难以理解,很难一下子看出来:
-
- 某个函数结构到底有哪些函数可以对他进行操作?
-
- 某个函数到底是用来操作哪些数据结构的?
-
- 任何两个函数之间存在怎样的低哦啊用关系?
-
- 结构化程序设计没有"封装"和"隐藏"的概念。要访问某个数据结构中的某个变量,就可以直接访问,那么当该变量的定义有改动的时候,就要把所有访问该变量的语句找出来修改,十分不利于程序的维护、扩充。
-
- 难以查错,当某个数据结构的值不正确时,难以找出到底是哪个函数导致的。
-
- 结构化程序设计中,随着程序规模的扩大,由于程序大量函数、变量之间的关系错综复杂,要抽取这部分代码,变得十分困难(无法重用)。
2、面向对象的程序设计
- 面向对象的程序设方法,能够较好的解决上述结构化程序设计的不足。
面向对象的程序 = 类 + 类 + ···· + 类
- 设计程序的过程,就是设计类的过程。
2.1、面向对象的程序设计
面向对象的程序设计方法:
- 将某类客观事物共同特点(
属性
)归纳出来,形成一个数据结构(可以用多个变量描述事物的属性);- 将这类事物所能进行的行为也归纳出来,形成一个个函数,这些函数可以用来操作数据结构(这一步叫"
抽象
")。
然后,通过某种语法形式,将数据结构和操作该数据结构的函数"捆绑
"在一起,形成一个"类
",从而使得数据结构和操作该数据结构的算法呈现出显而易见的紧密关系,这就是"封装
"。
面向对象的程序设计具有"抽象","封装","继承","多态"四个基本特点。
面向对象的程序模式:(如下图所示)
2.2、从客观事物抽象出类
- 将长、宽变量和设置长、宽,求面积,以及求周长的三个函数"封装"在一起,就能形成一个'矩形类'。
- 长、宽变量成为该"矩形类"的
"成员变量"
,三个函数成为该类的"成员函数"
。成员变量和成员函数统称为类的成员。
代码:
cpp
#include <iostream>
class CRectangle
{
public:
int w, h; // 成员变量
// 三个成员函数
int Area() {
return w * h;
}
int Perimeter() {
return 2 * (w + h);
}
void Init(int w_, int h_) {
w = w_; h = h_;
}
}; //必须有分号
int main()
{
int w,h;
CRectangle r; // r是一个对象
std::cout << "请输入w和h"<<std::endl;
std::cin >> w >> h;
r.Init(w, h);
std::cout << r.Area() << std::endl << r.Perimeter();
return 0;
}
//OUT:
请输入w和h
1
3
面积 = 3; 周长 = 8
通过类,可以定义变量。类定义出来的
变量
,也称为类的实例,就是我们所说的"对象"
。C++中,
类的名字就是用户定义的类型的名字。可以像使用基本类型那样来使用它。
CRectangle就是一种用户自定义的类型。
2.3、对象的内存分配
- 和结构变量一样,
对象
所占用的内存空间的大小,等于所有成员变量的大小之和。- 对于上面的CRectangle类,sizeof(CRectangle) = 8
- 成员函数会占用存储空间,但是一个类的成员函数在内存中只有一份,他被所有对象所共享,成员函数不会放入到对象里面。
(对象只包含成员变量,不包含成员函数。)
- 每个对象各有自己的存储空间。一个对象的某个成员变量被改变了,不会ing下昂另一个对象。
2.4、对象之间的运算
和结构变量一样,对象之间可以用 "=" 进行赋值,但是不能用"==","!=","<",">=","<="
,进行比较,除非这些运算符经过了"重载"。
2.5、使用类的成员变量和成员函数
用法1:对象名.成员名
cpp
CRectangle r1, r2;
r1.w = 5;
r2.Init(5, 4);
Init函数作用在r2上,即Init函数执行期间访问的w和h是属于r2这个对象的,执行r2.Init不会影响到r1。
用法2:指针->成员名
cpp
CRectangle r1,r2;
CRectangle * p1 = & r1;
CRectangle * p2 = & r2;
p1->w = 5;
p2->Init(5,4); //Init作用在p2指向的对象上
用法3:引用.成员名
cpp
CRectangle r2;
CRectangle & rr = r2;
rr.w = 5;
rr.Init(5,4); // rr的值变了,r2的值也会随着改变
void PrintRectangle(CRectangle & r)
{
cout << r.Area() << "," << r.Perimeter();
}
CRectangle r3;
r3.Init(5,4);
printRectangle(r3);
2.6、类成员的可访问范围
2.6.1、代码实例
cpp
#include <iostream>
#include <stdio.h>
// #include <string.h>
class CEmployee {
private:
char szName[30]; //名字
public :
int salary; //工资
void setName(char * name);
void getName(char * name);
void averageSalary(CEmployee e1,CEmployee e2);
};
void CEmployee::setName( char * name) {
strcpy(szName, name); //ok
}
void CEmployee::getName( char * name) {
strcpy(name, szName); //ok
}
void CEmployee::averageSalary(CEmployee e1,CEmployee e2){
salary = (e1.salary + e2.salary )/2;
}
int main(){
CEmployee e;
// strcpy(e.szName,"Tom1234567889"); //编译错, 不能访问私有成员
e.setName("Tom"); // ok
e.salary = 5000; //ok
return 0;
}
知识点扩充:C语言中字符串之间的比较【char*】/【string】
2.6.2、private的意义(隐藏的作用)
- 设置私有成员的机制,叫
"隐藏"
。- "隐藏 "的
目的是强制对成员变量的访问一定要通过成员函数进行,那么以后成员变量的类型等属性修改后,字需要更改成员函数即可。否则,所有直接访问成员变量的语句都需要修改。
- 如果上面的程序移植到内存空间紧张的手持设备上,希望将
szName改为char szName[5]
,若szName不是私有,那么就要找出所有类似strcpy(e.szName, "Tom1234567889");
,这样的语句进行修改,以防止数组越界。这样做很麻烦。- 如果将szName变为私有,那么程序中就不可能出现(除非在类的内部)
strcpy(e.szName, "Tom1234567889");
这样的语句,所有对szName的访问都是通过成员函数来进行,比如:e.setName("Tom");
,那么,就算szName
改短了,上面的语句也不需要找出来修改,只要改setName
成员函数,在里面确保不越界就可以了。
2.6.3、成员函数的重载及参数缺省
- 成员函数也可以重载
- 成员函数可以带缺省参数
- 案例如下:
cpp
#include <iostream>
class Location {
private:
int x, y;
public:
void init(int x = 0, int y = 0);
// 两个valueX是函数重载关系
void valueX(int val) { x = val; }
int valueX() { return x; }
};
int main() {
Location A, B;
A.init(5);
A.valueX(6);
std::cout << A.valueX() ;
return 0;
}
void Location::init(int x, int y)
{
x = x;
y = y;
}
// OUT
6
注意!!!
使用缺省参数需要注意避免有函数重载时的二义性
cpp
class Location {
private:
int x, y;
public:
void init(int x = 0, int y = 0);
两个valueX是函数重载关系
(这里存在二义性)
void valueX(int val = 0) { x = val; }
int valueX() { return x; }
};
int main() {
Location A;
A.valueX(); Error,编译起无法判断调用哪个valueX
return 0;
}