1 面向对象
1.1 为什么面向对象
- 相比于分而治之的结构化程序设计,强调大处着眼的面向对象程序设计思想,更适合于开发大型软件
- 得益于数据抽象、 代码复用等面向对象的固有特征,软件开发的效率获得极大地提升,成本却大幅降低
- 面向对象技术在数据库、网络通信、图形界面等领域的广泛应用,已催生出各种设计模式和应用框架
- 面向对象技术的表现如此出众,以至于那些原本并不直接支持面向对象特性的语言(例如C),也在越来越多地通过各种方法模拟一些面向对象的软件结构
1.2 什么是面向对象
- 万物皆对象,这是人类面对世界最朴素,最自然的感觉、想法和观点
- 把大型软件看成是一个由对象组成的社会
- 对象拥有足够的智能,能够理解来自其它对象的信息,并以适当的行为作出反应
- 对象能够从高层对象继承属性和行为,并允许低层对象从自己继承属性和行为等
- 编写程序的过程就是描述对象属性和行为的过程,凭借这种能力使问题域和解域获得最大程度的统一
- 面向对象的三大要件:封装、 继承和多态
1.3 怎样面向对象?
– 至少掌握一种面向对象的程序设计语言,如C++
– 深入理解封装、继承和多态等面向对象的重要概念
– 精通一种元语言,如UML,在概念层次上描述设计
– 学习设计模式,源自多年成功经验的积累和总结
2 类和对象
- 拥有相同属性和行为的对象被分成一组,即一个类
- 类可用于表达那些不能直接与内置类型建立自然映射关系的逻辑抽象
- 类是一种用户自定义的复合数据类型,即包括表达属性的成员变量,也包括表达行为的成员函数
- 类是现实世界的抽象,对象是类在虚拟世界的实例
2.1 类的定义与实例化
2.1.1 类的一般形式
2.1.2 访问控制限定符
- public
- protected
- private
访问控制限定符
- 在C++中,类(class)和结构(struct)已没有本质性的差别,唯一的不同在于
- 类的缺省访问控制属性为私有(private)
- 结构的缺省访问控制属性为公有(public)
- 访问控制限定符仅作用于类,而非作用于对象,因此同一个类的不同对象,可以互相访问非公有部分
- 对不同成员的访问控制属性加以区分,体现了C++作为面向对象程序设计语言的封装特性
#include <iostream>
using namespace std;
class Student
{
int m_age;
string m_name;
int m_no;
public:
Student(const string& name,int age,int no)
{
m_name = name;
m_age = age;
m_no = no;
}
void eat(const string& food)
{
cout << "eat " << food << endl;
}
void study(const string& course)
{
cout << "study " << course << endl;
}
void who(void)
{
cout << "name: " << m_name << endl;
cout << "age : " << m_age << endl;
cout << "school number: " << m_no << endl;
}
void setAge(int age)
{
if(age<0)
cout << "age wrong" << endl;
else
m_age = age;
}
void setNo(int no)
{
if(no < 0)
cout << "no wrong" << endl;
else
m_no = no;
}
void setName(const string& name)
{
m_name = name;
}
void foo(const Student& s)
{
cout << s.m_name << endl;
}
};
class Teacher
{
string m_name;
int m_age;
public:
void who(void)
{
cout << m_name << "," << m_age << endl;
}
};
int main()
{
/*
Student s;
s.setAge(25);
s.setName("zhang san");
s.setNo(1001);
s.who();
s.eat("noodle");
s.study("c++");
*/
Student s("zhang san",24,1001);
s.eat("noodel");
s.study("study");
Student s2("wang wu",35,2003);
// s2.setName("wang wu");
s.foo(s2);
Student* ps = new Student("zhao si",34,5654);
ps->who();
ps->eat("ham");
ps->study("Unix");
// -----------------------------
Teacher t1;
t1.who();
return 0;
}
2.1.3 构造函数
- 函数名和类名相同,且没有返回类型
- 在创建对象时自动被调用,且仅被调用一次
- 对象定义语句
- new操作符
- 为成员变量赋初值,分配资源,设置对象的初始状态
- 对象的创建过程
- 为整个对象分配内存空间
- 以构造实参调用构造函数
- 构造基类部分
- 构造成员变量
- 执行构造代码
2.1.4 类的声明和实例化分开
将类的声明、实现与使用分别放在不同的文件里
2.1.5 对象的创建与销毁
- 在栈中创建单个对象
类名 对象; // 注意不要加空括号
类名 对象 (实参表); 在栈中创建对象数组
类名 对象数组[元素个数];
类名 对象数组[元素个数] = {类名 (实参表), …};
类名 对象数组[] = {类名 (实参表), …};在堆中创建/销毁单个对象
类名* 对象指针 = new 类名;
类名* 对象指针 = new 类名 ();
类名* 对象指针 = new 类名 (实参表);
delete 对象指针;- 在堆中创建/销毁对象数组
类名* 对象数组指针 = new 类名[元素个数];
类名* 对象数组指针 = new 类名[元素个数] {类名 (实参表),…};
// 上面的写法需要编译器支持C++11标准
delete[] 对象数组指针;
实例:
student.h
// 声明Student类
#ifndef _STU_H
#define _STU_H
#include <string>
using namespace std;
class Student
{
string m_name;
int m_age;
int m_no;
public:
Student(const string& name,int age,int no);
Student();
void who();
void eat(const string& food);
void study(const string& course);
};
#endif // _STU_H
student.cpp
// 实现Student类
#include "02stu.h"
#include <iostream>
using namespace std;
Student::Student(const string& name,int age,int no)
{
m_name = name;
m_age = age;
m_no = no;
}
Student::Student()
{
m_name = "no name";
m_age = 34;
m_no = 234;
}
void Student::who()
{
cout << "name: " << m_name << endl;
cout << "age: " << m_age << endl;
cout << "no : " << m_no << endl;
}
void Student::eat(const string& food)
{
cout << "eat " << food << endl;
}
void Student::study(const string& course)
{
cout << "study " << course << endl;
}
main.cpp
#include "02stu.h"
#include <iostream>
using namespace std;
int main()
{
Student s("zhang san",24,2002);
Student s2;
s.who();
s.study("c++");
s.eat("noodle");
s2.who();
Student sa[5];
sa[0].who();
sa[4].who();
Student sa2[3] = {
Student("zhao si",34,33),
Student("li si",54,24)
};
sa2[0].who();
sa2[1].who();
sa2[2].who();
cout << sizeof(s) << endl;
Student *ps1 = new Student("wang wu",34,3566);
ps1->who();
delete ps1;
ps1 = new Student[5];
ps1[0].who();
ps1[4].who();
delete[] ps1;
ps1 = new Student[3] {
Student("wang yi",34,234),
Student("wang er",35,355),
Student("wang san",36,356)
};
ps1[0].who();
ps1[2].who();
delete[] ps1;
return 0;
}
练习:
实现一个电子时钟类,其构造函数接受当前系统时间,以秒为单位持续运行
#include <iostream>
#include <cstdio>
using namespace std;
class Clock {
public:
Clock (time_t t) {
tm* local = localtime (&t);
m_hour = local->tm_hour;
m_min = local->tm_min;
m_sec = local->tm_sec;
setbuf (stdout, NULL);
}
void run (void) {
for (;;) {
show ();
tick ();
}
}
private:
void show (void) {
printf (" %02d:%02d:%02d",
m_hour, m_min, m_sec);
// fflush (stdout);
}
void tick (void) {
sleep (1);
if (++m_sec == 60) {
m_sec = 0;
if (++m_min == 60) {
m_min = 0;
if (++m_hour == 24)
m_hour = 0;
}
}
}
int m_hour, m_min, m_sec;
};
int main (void) {
Clock clock (time (NULL));
clock.run ();
return 0;
}
2.2 构造函数和初始化表
2.2.1 构造函数的重载
- 构造函数也可以通过参数表的差别化形成重载
- 重载的构造函数通过构造实参的类型选择匹配
- 不同的构造函数版本表示不同的对象创建方式
- 使用缺省参数可以减少构造函数重载版本数量
- 某些构造函数具体特殊的意义
- 缺省构造函数:按缺省方式构造
- 类型转换构造函数:从不同类型的对象构造
- 拷贝构造函数:从相同类型的对象构造
2.2.2 缺省构造函数
- 缺省构造函数亦称无参构造函数,但其未必真的没有任何参数,为一个有参构造函数的每个参数都提供一个缺省值,同样可以达到无参构造函数的效果
- 如果一个类没有定义任何构造函数,那么编译器会为其提供一个缺省构造函数
- 对基本类型的成员变量,不做初始化
- 对类类型的成员变量和基类子对象,调用相应类型的缺省构造函数初始化
- 对于已经定义至少一个构造函数的类,无论其构造函数是否带有参数,编译器都不会为其提供缺省构造函数