首页 > 代码库 > C++学习笔记_four_day
C++学习笔记_four_day
Oop编程示例:
1、经典案例:武林趣事
某年某月某日
张无忌 偷袭了 令狐冲
经 平一指 诊断 令狐冲 受伤了
分析:
如何用程序实现上述事件?
(1)分析事件中有哪些名词和动词
(2)从类的角度将这些名次和动词联系在一起
案例设计:
(1)需要设计几个类?
(2)每个类的属性和行为是什么?
(3)如何使用这些类的对象?
//Fight.cc #include <iostream> using namespace std; // 1.设计类 /** * Hero 类 **/ class Hero { string m_name; // 姓名 bool m_status; // 身体状态 public: Hero(string name):m_name(name) { m_status = true; } ~Hero(){} // 偷袭 void hurt(Hero &h) { cout << m_name << " 偷袭了 " << h.m_name << endl; h.m_status = false; return ; } // 诊断 void check(const Hero &h) { cout << " 经 " << m_name << " 诊断 " << h.m_name << (h.m_status?"无大碍":"受伤了") << endl; return ; } }; int main(int argc,char **argv) { //2.创建对象 Hero h1("张无忌"); Hero h2("令狐冲"); Hero h3("平一指"); //3.使用对象(调用成员函数) h1.hurt(h2); h3.check(h2); return 0; }
-
实战演练:
小明在老盛兴吃中饭
小明向服务员点了一份糖醋排骨,
服务员上了一份糖醋排骨,
小明吃完中饭,向服务员支付了20元餐费,离开了老盛兴。
//Lunch.cc #include <iostream> using namespace std; /** * 服务员类 **/ class Waiter { string m_name; // 姓名 public: Waiter(string name):m_name(name){} ~Waiter(){} string GetName(){ return m_name;} void server(int count,string name); // 上菜 }; // 上菜 void Waiter::server(int count,string food) { cout << m_name << " 上了 " << count << " 份 "<< food << endl; return ; } /** * 顾客类 **/ class Guest { string m_name; // 姓名 public: Guest(string name):m_name(name){} ~Guest(){} void order(Waiter &w); // 点菜 void pay(string name); // 付款 }; // 点菜 void Guest::order(Waiter &w) { int count; //菜的个数 string food; //菜的名称 cout << " ****点菜的过程*****" << endl; cout << " 输入菜的份量:"; cin >> count; cout << " 输入菜的名称:"; cin >> food; cout << " *************** " << endl; cout << endl; cout << m_name << " 向 服务员 " << w.GetName()<< " 点了 " << count << " 份 " << food << endl; cout << endl; cout << "**过了若干分钟之后**" << endl; cout << endl; // 服务员上菜 w.server(count,food); return ; } // 付款 void Guest::pay(string name) { double Money = 0; cout << m_name << " 吃完了中饭" << endl; cout << endl; cout << " ****结帐的过程*****" << endl; cout << "输入小明的付款金额:"; cin >> Money; cout << " *************** " << endl; cout << endl; cout << m_name << "向 服务员 "<< name << " 支付了 "<< Money << " 元餐费," << "离开了老盛兴" <<endl; return ; } int main(int argc,char **argv) { //2.创建对象 Waiter w("小吴"); Guest g("陶嗣远"); //3.使用对象 g.order(w); g.pay(w.GetName()); return 0; }
-
实战演练:小甜甜在上嵌上课
马老师 在 项目一组 教 C++课程;
江老师 在 项目四组 教 ARM课程;
小甜甜 上午 在 项目一组 上 C++课程,
下午 在 项目四组 上 ARM课程。
1 //Listen.cc 2 #include <iostream> 3 using namespace std; 4 5 //1.设计类 6 /** 7 * 教师类 8 **/ 9 class Teacher 10 { 11 string m_name; // 姓名 12 string m_addr; // 上课地点 13 string m_course; // 课程名称 14 public: 15 Teacher(string name,string addr,string course):m_name(name),m_addr(addr),m_course(course){} 16 ~Teacher(){} 17 18 string GetAddr() 19 { 20 return m_addr; 21 } 22 23 string GetCourse() 24 { 25 return m_course; 26 } 27 28 // 授课 29 void teach(); 30 }; 31 32 // 授课 33 void Teacher::teach() 34 { 35 cout << m_name << " 在 "<< m_addr << " 教 "<< m_course << " 课程"<< endl; 36 return ; 37 } 38 39 /** 40 * 学生类 41 **/ 42 class Student 43 { 44 string m_name; 45 public: 46 Student(string name):m_name(name){} 47 ~Student(){} 48 49 // 上课 50 void Listen(string szTime,Teacher &t); 51 }; 52 53 // 上课 54 void Student::Listen(string szTime,Teacher &t) 55 { 56 cout << m_name << " " << szTime << " 在 " << t.GetAddr()<< " 上 " << t.GetCourse() 57 << endl; 58 59 return ; 60 } 61 62 int main(int argc,char **argv) 63 { 64 //2.创建对象 65 Teacher t1("马老师","项目一组","C++"); 66 Teacher t2("江老师","项目四组","ARM"); 67 Student s("小甜甜"); 68 69 //3.使用对象 70 t1.teach(); 71 t2.teach(); 72 73 s.Listen("上午",t1); 74 s.Listen("下午",t2); 75 return 0; 76 }
4.实战演练:艾米在京东商城网购球鞋
艾米在京东商城订购了一双球鞋,
京东商城给艾米发了一双球鞋。
艾米对球鞋很满意,于是支付了货款;
艾米对球鞋很不满意,于是就退货了。
1 //WebShop.cc 2 #include <iostream> 3 using namespace std; 4 5 /** 6 * 网店 7 **/ 8 class WebShop 9 { 10 string m_name; 11 public: 12 WebShop(string name):m_name(name){} 13 ~WebShop(){} 14 15 string GetName(){ return m_name;} 16 17 // 发货 18 void send(string szGuest,int count,string brand,string goods); 19 }; 20 21 // 发货 22 void WebShop::send(string szGuest,int count,string brand,string goods) 23 { 24 cout << m_name << " 给 "<< szGuest << " 发 "<< count << " 件 " 25 << brand << " " << goods<< endl; 26 return ; 27 } 28 29 /** 30 * 顾客类 31 **/ 32 class Guest 33 { 34 string m_name; 35 public: 36 Guest(string name):m_name(name){} 37 ~Guest(){} 38 39 // 订购 40 void order(WebShop &w); 41 // 商品的处理 42 void deal(string goods); 43 }; 44 45 // 订购 46 void Guest::order(WebShop &w) 47 { 48 int count; 49 string brand; 50 string goods; 51 52 cout << "**订货的过程**" << endl; 53 cout << "填写商品的个数:"; 54 cin >> count; 55 cout << "填写商品的品牌:"; 56 cin >> brand; 57 cout << "填写商品的种类:"; 58 cin >> goods; 59 cout << "*************" << endl; 60 cout << endl; 61 62 cout << m_name << " 在 "<< w.GetName() << " 订购了 "<< count << " 件 " 63 << brand << " " << goods 64 << endl; 65 66 cout << "**经过3-5个工作日***" << endl; 67 cout << endl; 68 69 w.send(m_name,count,brand,goods); 70 71 deal(goods); 72 73 return ; 74 } 75 76 77 // 商品的处理 78 void Guest::deal(string goods) 79 { 80 bool result; 81 82 cout << "**检查商品的质量**"<<endl; 83 cout << "输入对商品的评价:"; 84 cin >> result; 85 cout << "***************"<<endl; 86 87 cout << m_name << " 对 "<< goods << (result?"满意,支付货款":"不满意,退货") << endl; 88 89 return ; 90 } 91 92 93 int main(int argc,char **argv) 94 { 95 WebShop w("京东商城"); 96 Guest g("吴尚奇"); 97 98 g.order(w); 99 return 0; 100 }
Oop编程总结:
-
熟悉oop的编码流程
-
Oop的核心:设计类
-
设计的注意点:
-
确定类与类之间是否存在交互
----决定了成员函数的形参是对象还是普通变量
-
确定事件的主动发生方和被动接收方
---决定了事件由主动发生方描述
<style></style>
思考题:面向过程和面向对象编程的区别
<style></style>封装性上
面向对象的封装将一系列的数据和方法集中在类中
面向过程的封装, 方法一般不做封装, 数据用Struct封装, 方法和数据分离
代码复用性上
面向对象利用继承的方式复用
面向过程只能以普通的函数复用
其它的细节区别, 比如overload, overwrite之类的区别, 属于语义上的区别, 不影响本文的主题.
面向对象之弊,面向过程之优
1. OO的特点, 就是把数据和逻辑封装成一个整体, 带来了强耦合的问题.
2. OP的特点, 数据和逻辑分开, 绝对的松耦合, 但封装性不够.
上述两个东东的互斥的特性, 给我们带来了非常重要的提示:
模块之间的设计, 为了维护代码(修改bug, 增加新功能, 独立协调成员工作). 严重关注耦合度, 要求尽量即插即用, 模块间完全独立. 这个时候我们需要尽量的按照OP的思路. 模块和模块之间的协作, 务必按照OP的思路. 逻辑分离得非常彻底.
内部单元的设计, 尽量封装良好, 利用OO的思想, 将各个零部件整理成一个类整体.
比如, 电脑的组装, 在整体上, 我们是通过电源线将机箱和插座, 以及显示器连接, 这个上面的设计思路, 是按照OP来的直观的连接流程(连接线可以视为胶合层, 《Unix编程艺术》上明确的要求这个层面尽量薄), 但机箱内部, 各个零件, 如DVD, 硬盘, 主板, 都是一个分装完美的对象, 包含数据, 也包含处理的动作. 这个可以实现插件的管理模式.
特别强调的一点, OP和OO的设计思路, 主要是看在哪个层次, "模块"和“内部单元”实质上是一个非常抽象的概念, 在某个层面上, “内部单元”可能是一个“模块”, 而在更大的层面上, "模块"也被当做一个“内部单元来处理”, 所以, 如何选择OO的设计方式, 还是OP的设计方式, 需要视情况而定. 我们所设计的任何一个点, 都停留在产品金字塔结构的一个“内部单元”中, 同时也停留在一个"模块中". 下图概括了上文的表述内容:
<style></style>
-
Const
常对象和常成员函数
常成员函数:
1 2 #include<iostream> 3 using namespace std; 4 5 class A 6 { 7 int m_data; 8 public: 9 A(int data):m_data(data){} 10 ~A(){} 11 //常成员函数,const在参数列表之后 12 //作用:限制成员数据的修改 13 void show() const 14 { 15 cout<<"show() const"<<endl; 16 // cout<<" m_data= "http://www.mamicode.com/<<(++m_data)<//成员函数不能进行修改,将++去掉之后就不会报错了 17 return ; 18 } 19 20 //const也能构成函数的重载 21 void show() 22 { 23 cout<<"show() const"<<endl; 24 cout<<" m_data= http://www.mamicode.com/"<<(++m_data)<<endl; 25 return ; 26 } 27 }; 28 29 int main() 30 { 31 A a(10); 32 //普通函数 优先调用 普通的同名的成员函数 33 a.show();//调用方式和普通函数一样 34 return 0; 35 }
普通对象/常对象和普通成员函数/常成员函数的关系
普通对象 ---优先调用--->普通成员函数
常对向 ----只能调用 ---->常成员函数
作业:
设计一个字符串类String,要求如下:
1.实现4种构造函数
String s1;
String s2(s1);
String s3("wushangqi");
String s4(4,‘a‘); // 用4个‘a‘初始化
2.实现empty(),size(),resize()函数;
3.只允许有一个成员char *p;
参考资料:
C++中string类中函数的定义如下:
bool empty()const; //当前字符串是否为空
int size()const; // 返回当前字符串大小
void resize(int len,char c);//设置字符串当前大小置为len,并用字符c填充不足的部分
1 //String.cc 2 #include <string.h> 3 4 #include <iostream> 5 using namespace std; 6 7 /* 8 1.实现4种构造函数 9 String s1; 10 String s2(s1); 11 String s3("shangqian"); 12 String s4(4,‘a‘); // 用4个‘a‘初始化 13 2.实现empty(),size(),resize()函数; 14 3.只允许有一个成员char *p; 15 */ 16 17 // 模仿 实现 系统提供的string类 18 class String 19 { 20 char *p; 21 public: 22 String(); 23 String(const String &s); 24 String(const char *str); 25 String(int cnt,char ch); 26 ~String(){} 27 28 int size()const; 29 bool empty()const; 30 void resize(int len,char c); 31 32 void show(); 33 }; 34 35 // 1.实现无参的构造函数 36 String::String() 37 { 38 p = NULL; 39 } 40 41 // 2.实现拷贝构造函数 42 String::String(const String &s) 43 { 44 // 1.知道字符串的长度 45 int cnt = strlen(s.p); 46 47 // 2.p申请空间 48 p = new char[cnt+1]; 49 50 // 3.拷贝值 51 strcpy(p,s.p); 52 } 53 54 // 3.带一个常字符串的构造函数 55 //String s3("shangqian"); 56 String::String(const char *str) 57 { 58 // 1.知道字符串的长度 59 int cnt = strlen(str); 60 61 // 2.p申请空间 62 p = new char[cnt+1]; 63 64 // 3.拷贝值 65 strcpy(p,str); 66 } 67 68 // 4.带两个参数的构造函数 69 String::String(int cnt,char ch) 70 { 71 // 1.p申请空间 72 p = new char[cnt+1]; 73 74 // 2.拷贝值 75 int i; 76 for(i=0; i<cnt; ++i) 77 { 78 p[i]=ch; 79 } 80 } 81 82 // 5.判断字符串是否为空 83 bool String::empty()const 84 { 85 // NULL == 0 == ‘\0‘ 86 return p; 87 } 88 // 6.返回字符串的长度 89 int String::size()const 90 { 91 return (empty()?strlen(p):0); 92 } 93 94 // X.显示字符串内容 95 void String::show() 96 { 97 cout << "字符串内容为:" 98 << p << endl; 99 100 return ; 101 } 102 103 int main(int argc,char **argv) 104 { 105 String s1; 106 107 // 0?"是":"不是" 108 // 位置需要根据实际情况调整 陷阱 109 cout << " String s是否为空:"<< (s1.empty()?"不是":"是")<< endl; 110 111 cout << " String s的长度:"<< s1.size() << endl; 112 113 cout << "*****String s2(\"wushangqi\");*****"<< endl;//注意转义字符‘\’ 114 115 String s2("shangqian"); 116 s2.show(); 117 118 //注意:陷阱等效与0?“不是”:“是” 119 cout << " String s是否为空:"<< (s2.empty()?"不是":"是")<< endl; 120 121 cout << " String s的长度:" 122 << s2.size() << endl; 123 124 cout << "*****String s3(5,‘c‘);*****"<< endl; 125 126 String s3(5,‘c‘); 127 s3.show(); 128 129 cout << " String s是否为空:"<< (s3.empty()?"不是":"是") << endl; 130 131 cout << " String s的长度:" << s3.size() << endl; 132 133 cout << "*****String s4(s2);*****"<< endl; 134 135 String s4(s2); 136 s4.show(); 137 138 cout << " String s是否为空:"<< (s4.empty()?"不是":"是")<< endl; 139 140 cout << " String s的长度:"<< s4.size() << endl; 141 142 return 0; 143 }
<style></style>
补:拷贝构造函数
拷贝构造函数,经常被称作X(X&),是一种特殊的构造函数,他由编译器调用来完成一些基于同一类的其他对象的构件及初始化。它的唯一的一个参数(对象的引用)是不可变的(因为是const型的)。这个函数经常用在函数调用期间于用户定义类型的值传递及返回。拷贝构造函数要调用基类的拷贝构造函数和成员函数。如果可以的话,它将用常量方式调用,另外,也可以用非常量方式调用。
在C++中,下面三种对象需要拷贝的情况。因此,拷贝构造函数将会被调用。
1). 一个对象以值传递的方式传入函数体
2). 一个对象以值传递的方式从函数返回
3). 一个对象需要通过另外一个对象进行初始化
以上的情况需要拷贝构造函数的调用。如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作赋共同实现的。描述拷贝构造函数和赋值运算符的异同的参考资料有很多。
拷贝构造函数不可以改变它所引用的对象,其原因如下:当一个对象以传递值的方式传一个函数的时候,拷贝构造函数自动的被调用来生成函数中的对象。如果一个对象是被传入自己的拷贝构造函数,它的拷贝构造函数将会被调用来拷贝这个对象这样复制才可以传入它自己的拷贝构造函数,这会导致无限循环。
除了当对象传入函数的时候被隐式调用以外,拷贝构造函数在对象被函数返回的时候也同样的被调用。换句话说,你从函数返回得到的只是对象的一份拷贝。但是同样的,拷贝构造函数被正确的调用了,你不必担心。
如果在类中没有显式的声明一个拷贝构造函数,那么,编译器会私下里为你制定一个函数来进行对象之间的位拷贝(bitwise copy)。这个隐含的拷贝构造函数简单的关联了所有的类成员。许多作者都会提及这个默认的拷贝构造函数。注意到这个隐式的拷贝构造函数和显式声明的拷贝构造函数的不同在于对于成员的关联方式。显式声明的拷贝构造函数关联的只是被实例化的类成员的缺省构造函数除非另外一个构造函数在类初始化或者在构造列表的时候被调用。
拷贝构造函数是程序更加有效率,因为它不用再构造一个对象的时候改变构造函数的参数列表。设计拷贝构造函数是一个良好的风格,即使是编译系统提供的帮助你申请内存默认拷贝构造函数。事实上,默认拷贝构造函数可以应付许多情况。