聪明在于学习,天才在于积累。所谓天才,实际上是依靠学习。
文章目录
- 概述
- 正文
- 面向对象编程和面向过程编程
- 类和对象
- 类的组成
- 公共接口
- 类声明
- 访问控制
- 封装
- 类和结构体
- 类定义
概述
C++是门包罗万象的语言,它将各门类的编程思想杂糅,最终造就了它繁琐但几乎无所不能的特点。C++的编程思想主要有四类:面向过程编程(脱胎于c语言),面向对象编程(OOP),STL和泛型编程。就像蛋糕要一块一块啃,学C++也要一部分一部分学。本专栏用《C++ primer plus》中OOP的部分带读者一探OOP思想的魅力。
正文
面向对象编程和面向过程编程
举个例子,如果你要记录一个班学生的成绩。如果用面向过程编程的思想,需要创建一个字符串数组来表示学生的名字,一个int数组来表示学生的分数;如果用面向对象编程,你只需要创建一个表示学生的类,这个类表示一个模板,就像一个模具,你可以用这个模具来定制属于自己的对象。就像这样:
class student
{
private:
string name;
int grade;
public:
student(string nm, int g) :name(nm), grade(g) {};
};
int main(void)
{
student s1((string)"abc", 100);
}
面向对象编程能清楚的表现“A有B”或“A能做B“”的关系。比如一只狗能跑,跳,叫…的能力;一个学生有自己的名字和分数。;面向过程编程则是先做一步,再做下一步,一步一步完成问题。这也就是为什么这两类思想有注重“对象”和“过程”之分。
类和对象
类是一种将抽象转换为用户定义类型的C++工具。说白了,编写程序的人可以将自己脑子里想象出的一类事物用代码的形式写出来,定义为一个新的类型并像使用int,long一样使用它(int也是将抽象的整数类型写成一个类型)。比如,一个名叫Dog的类可能代表狗这一类型。
对象是类的具体化。比如我家养了一只叫做金毛的狗,金毛就可以是狗这一类的对象。
类的组成
类由成员组成,成员分为属性和方法。
- 属性是这个类所拥有的事物,比如狗可以有四条腿,两只耳朵。属性是这个类的数据部分。
- 方法是这个类可以进行的动作,比如狗可以奔跑,可以吠叫。方法是这个类的函数部分。
在编写类时,类规范由两个部分组成: - 类声明:包含类的属性部分和方法部分中函数的声明。
- 类方法定义:包含方法部分函数的实现。
公共接口
对OOP思想有过接触的读者会知道,秉承OOP思想编写的main函数中基本只会调用各个对象的方法来使用这些对象。对于某个对象而言,它可能需要与其他对象互动。总之,一个类必定会定义至少一个需要被外界使用的方法。这些需要被外界使用的方法就被称作公共接口。公共是public的意思,这个关键词接下来会讲到。
接口是一个事物与其他事物进行交互的部分。例如,我们在充电时需要用到插孔,如果没有插孔,就算我们离电线只有一墙之隔,也不可能从电线里拿出电来,此时插孔就可以认为是一个接口。
类声明
下面是一个类声明的例子(类定义和普通的函数定义差不多):
//stock00.h --Stock class interface
#ifndef STOCK00_H_//类声明放到头文件中
#define STOCK00_H_
#include<String>
using namespace std;
class Stock //class declaration
{
private://这个关键字代表其下是private成员
string company;
long shares;
double share_val;
double total_val;
void set_tot() {
total_val = shares * share_val;
}
public://这个关键字代表其下是public成员
void acquire(const string& co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
Stock(const string& co, long n, double pr);
Stock();
~Stock();
}; //注意末尾的分号
#endif
剖析这个例子:
- 将类声明放到头文件中。就像在C语言代码规范中将函数声明放到头文件,类声明也宜放在头文件中。
- class关键字代表这是一个类。
- private,public关键字涉及访问控制,暂时不用管。
- 注意类声明末尾要加分号。
- public关键词下的方法是公共接口,但set_tot()不是。
- 最后三个方法分别是两个构造函数和一个析构函数,这两类是极其特殊的没有返回值的函数,在后面会介绍。
访问控制
上述程序中的public(公共),private(私有)关键字体现了OOP中访问控制的思想。被标识为private的属性和方法不能被外部程序直接使用(例如main不能直接使用Stock的set_tot()方法),对于这一点,建议读者自己复制上面的代码放到IDE里试一下。外部程序只能通过public关键字下的方法来使用private成员,因此公共接口就是外部程序与private成员之间的桥梁。这一点意义重大,因为公共接口可以挡掉那些不合法或有害的输入,保证合理的使用private成员。类成员的默认访问控制是private。
事实上,还有第三个关键字protected(保护),这一关键字涉及类继承,将在之后提到。
封装
将实现细节与抽象分开被称为封装。在写出一个类声明时,我们并未想到全部的实现细节,我们想的是这个类能做什么;当外部程序使用这个类时,他们也只是知道这个类能做这件事,而不知道全部的实现细节,这也是一个判断封装的一个小技巧。上面提到,在OOP风格的程序中,main只调用对象的公共接口也是因为实现细节被封装而不能直接调用。封装提供了数据的安全性与隐蔽性,也让以后修改程序时无须修改接口。
类和结构体
在C++中对结构体功能进行了拓展,使其和类几乎完全相同,唯一不同的是struct的默认访问控制是public。但在实际使用中为了方便,我们一般只将struct实现为纯粹的数据对象而不带有方法。
类定义
类定义实质上和函数定义几乎相同,除了两点:
- 使用作用域解析运算符(::)表示函数使用的类。例如在定义Stock类的acquire方法时,要这么写:
void Stock::acquire(const string& co, long n, double pr)
{
//方法定义
}
- 类方法可以访问类的private组件(这是毋庸置疑的!)。
下面是Stock类的一个类定义,建议读者照着敲一遍代码(至少笔者都敲了一遍):
//stock00.cpp --implementing the Stock class
//version 00
#include<iostream>
#include "stock00.h"
void Stock::acquire(const string& co, long n, double pr)
{
company = co;
if (n < 0)//对不合理数据的输入的条件测试保证了程序的健壮性
{
cout << "Number of shares can't be negative;"
<< company << " shares set to 0.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
void Stock::buy(long num, double price)
{
if (num < 0)
{
cout << "Number of shares purchased can't be negative."
<< "Transaction is aborted.\n";
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num, double price)
{
if (num < 0)
{
cout << "Number of shares sold can't be negative."
<< "Transaction is aborted.\n";
}
else if (num > shares)
{
cout << "You can't sell more than you have!"
<< "Transaction is aborted.\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
/*void Stock::show()
{
cout << "Company:" << company
<< "Shares:" << shares << '\n'
<< "Shares Price:$" << share_val
<< "Total Worth:$" << total_val << '\n';
}*/
void Stock::show()
{
using std::cout;
using std::ios_base;
//set format to #.###
ios_base::fmtflags orig = cout.setf(ios_base::fixed, ios_base::floatfield);
std::streamsize prec = cout.precision(3);
cout << "Company:" << company
<< "Shares:" << shares << '\n';
cout << "Share Price:$" << share_val;
//set format to #.##
cout.precision(2);
cout << "Total Worth:$" << total_val << '\n';
//restore original format
cout.setf(orig, ios_base::floatfield);
cout.precision(prec);
}
Stock::Stock(const string& co, long n,double pr)
{
company = co;
if (n < 0)
{
std::cerr << "Number of shares can't be nagetive;"
<< company << "shares set to 0.\n";
shares = 0;
}
else
shares = n;
share_val = pr;
set_tot();
}
Stock::Stock()
{
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::~Stock()
{
cout << "Bye:" << company << '!\n';
}
我是霜_哀,在算法之路上努力前行的一位萌新,感谢你的阅读!如果觉得好的话,可以关注一下,我会在将来带来更多更全面的知识讲解!