class 类
C实现
typedef struct point { float x;
float y;
} Point;
void print(const Point* p){
printf(“%d %d\n”,p->x,p->y);
}
void move(Point* p,int dx, int dy){
p->x += dx;
p->y += dy;
}
Point a;
Point b;
a.x = b.x = 1;
a.y = b.y = 1;
move(&a,2,2);
print(&a);
print(&b);
C++封装
class Point {
public:
void init(int x, int y);
void move(int dx, int dy);
void print() const;
private: //可以保护坐标
int x;
int y;
};
// 函数返回 类名::函数(){} <Class Name>::<function name>
// ::<function name> 不属于任何一个类,全局函数, ::a全局变量
/*
void S::f() {
::f(); // Would be recursive otherwise!
::a++; // Select the global a
a--; // The a at class scope
}
*/
void Point::init(int ix, int iy){
x = ix;
y = iy;
}
void Point::move(int dx,int dy){
x+= dx;
y+= dy;
}
void Point::print() const { // const 代表不能改成员变量,只能访问
cout << x << ' ' << y << endl;
}
Point a; a.init(1,2);
a.move(2,2);
a.print();
Objects = Attributes + Services
将数据和操作封装在一起,要求只能用操作与对象交流,不能直接访问到数据本身
实现 stash container
container的要求
- 可以装其他对象的对象。
- 需要满足基本操作:put(), get() 。
- 可以动态增长。
- 无类型要求
- 只能存储同样类型的对象
- add() , fetch()
- Expanded when needed
struct 默认 public , class 默认 private 。
struct Stash {
int size; // Size of each space
int quantity; // Number of storage spaces
int next; // Next empty space
// Dynamically allocated array
unsigned char* storage;
// Functions!
void initialize(int size);
void cleanup();
int add(const void* element);
void* fetch(int index);
int count();
void inflate(int increase);
};
class Stash {
int size; // Size of each space
int quantity; // Number of storage spaces
int next; // Next empty space
// Dynamically allocated array
unsigned char* storage;
void inflate(int increase); // 当大小不够时增加,实现动态增长的核心函数
public:
Stash(int size);
~Stash(); // 对象消亡时的动作
int add(const void* element); // 加对象,只能加到最后
void* fetch(int index); // 根据坐标查看对象
int count(); // 对象数量
};
设计代码:
声明放在 .h 文件里,具体实现放在同名的 .cpp 文件(#include “.h”)里。
- Class declaration and prototypes in that class are in the header file (.h).
- All the bodies of these functions are in the source file (.cpp).
this : 隐藏指针,指向 调用的对象
对象存储时只存成员变量,不存函数,函数公用放在编译器代码段里,调用函数时需要 this 指针把该对象的变量带入函数。
买票机器: Ticket Machine
- 输入:用户给的钱
- 输出:余票数量和找的钱
Turn it into code
class 类 是一种概念,
object 对象 是类的具体实现,实例。
OOP 的特点:
- 所有都是对象.
- A program is a bunch of objects telling each other what to do by sending messages. 调用函数实现用户需求
- Each object has its own memory made up of other objects. 每个对象有自己的内存
- Every object has a type. 每个对象有自己的类型 class
- All objects of a particular type can receive the same messages.
契约精神:
The header is a contract between you and the user of your code.
.cpp 一个编译的单元
.h variables,function prototypes class/struct ,declaration
#include ""会先在本地目录下搜索,<>直接在缺省目录下搜索
#ifndef HEADER_FLAG
#define HEADER_FLAG
// Type declaration here...
#endif // HEADER_FLAG
相当于
#pragma once
// Type declaration here...
对复杂的项目,Makefile多文件编译
example:
包含三个文件的项目:main.c, sum.c, sum.h 并且 sum.h included in both .c files
sum : main.o sum.o
gcc –o sum main.o sum.o
main.o : main.c sum.h
gcc –c main.c
sum.o : sum.c sum.h
gcc –c sum.c
use built-in macros to get another (shorter) equivalent makefile
sum : main.o sum.o
gcc –o $@ main.o sum.o // $@ 引用cmd的名字“sum”
main.o sum.o : sum.h
gcc –c $*.c // $* 历遍引用cmd中的名字“main.o” “sum.o”
对象的行为
构造和析构函数
构造函数
The name of the constructor is the same as the name of the class.
class Point {
public:
Point(); // 缺省构造函数,没有任何参数;可以有参数
~Point(); // 析构函数,对象生命周期结束时自动调用
void init(int x, int y);
void print() const;
void move(int dx, int dy);
private:
int x;
int y;
};
Point a;
a.init(1,2);
a.move(2,2);
a.print();
“auto” default constructor
如果你定义了一个构造函数,编译器会保证这个函数正确使用,且一定被使用(写case时需要小心);如果没有定义构造函数,编译器会帮你构造一个缺省构造函数。
析构也有类似的“auto”。
设计时钟: Clock display
- 输入:–
- 输出:时钟随时间变化
抽象 Abstraction:
将小时和分钟都抽象为进位的类
模块化 Modularization:
小时和分钟两个对象模块之间的交互
Object & Classes
Class Diagram
Initialization Member Init - Directly initialize a member
class Point {
private:
const float x, y;
Point(float xa = 0.0, float ya = 0.0){
y = ya;
x = xa;
};
};
- Initializer list
class Point {
private:
const float x, y;
Point(float xa = 0.0, float ya = 0.0)
: y(ya), x(xa){}
};
给了成员变量调用构造函数的机会,而不是赋值,成员在对象构造之前初始化。
需要注意列表里的顺序,以防有先后依赖关系。
拷贝构造函数
void f() {
Stash students();
…
}
可以是一个函数声明,也可以是调用Stash类的缺省构造函数。
认为是函数声明Stash students(),调用Stash类的缺省构造函数Stash students
// Currency as pass-by-value argument
void func(Currency p) {
cout << "X = " << p.dollars();
}
...
Currency bucks(100, 0);
func(bucks); // bucks is copied into p
bucks 被复制到 p 中。
在定义时刻,把一个对象的值拷贝到另一个对象中使用 copy constructor
// copy constructor
T::T(const T&);
拷贝构造函数会自动生成,如果你没有编写相应的程序。
若 class 中存在指针,
class Person {
public:
Person(const char *s);
~Person();
void print();
// ... accessor functions
private:
char *name; // char * instead of string
//... more info e.g. age, address, phone
};
创建拷贝构造函数时的两种写法
重载 overloading
同名的函数可以被反复创造
C语言中不允许函数重名。
void print(char * str, int width); // #1
void print(double d, int width); // #2
void print(long l, int width); // #3
void print(int i, int width); // #4
void print(char *str); // #5
print("Pancakes", 15);
print("Syrup");
print(1999.0, 10);
print(1999, 12);
print(1999L, 15);
auto-cast
根据类型转换的规则自动调用函数
void f(short i);
void f(double d);
f('a'); // f(short i);
f(2); // f(short i);
f(2L);
f(3.2); // f(double d);
Default arguments
Stash(int size, int initQuantity = 0);
Stash s(20); // size = 20, initQuantity = 0.
defaults must be added from right to left.
int harpo(int n, int m = 4, int j = 5);
int chico(int n, int m = 6, int j); //illeagle
int groucho(int k = 1, int m = 2, int n = 3);
beeps = harpo(2);
beeps = harpo(1,8);
beeps = harpo(8,7,6);
const object
Constant in object
不能修改 const object 中成员函数的值
int Date::set_day(int d){
//...error check d here...
day = d; // ok, non-const so can modify
}
int Date::get_day() const {
day++; //ERROR modifies data member
set_day(12); // ERROR calls non-const member
return day; // ok
}
在头文件和 .cpp 文件中都要写 const
Const and non-const objects:
// non-const object
Date when(1,1,2001); // not a const
int day = when.get_day(); // OK
when.set_day(13); // OK
// const object
const Date birthday(12,25,1994); // const
int day = birthday.get_day(); // OK
birthday.set_day(14); // ERROR
Constant in class
在类中定义常量的变量,只允许初始化
class A {
const int i;
};
has to be initialized in initializer list of the constructor
编译器会避免为const分配内存
class HasArray {
const int size;
int array[size]; // ERROR! 编译时还不能确定 size 的值。
...
};
Make the const value static:
static const int size = 100;
class HasArray {
int array[size]; // OK!
...
};
Or use “anonymous enum” hack:
Class HasArray{
enum { size = 100 };
int array[size]; // OK!
…
}
对象变量
对象作为参数或返回值
// 作为参数
void f(Student i); // 会创建临时的Student 对象
void f(Student *p); // 最好加上 const 如果不想对原本的对象进行修改
void f(Student& i); // 最好加上 const 如果不想对原本的对象进行修改
// 作为返回值
Student f(); // 会创建临时的Student 对象
Student* f(); // 确保指向确切的地址
Student& f(); // 确保引用确切的地址
希望不同模块之间松耦合(简耦合),只依靠函数交流。
紧耦合的例子:
char *foo()
{
char *p;
p = new char[10];
strcpy(p, "something");
return p;
}
void bar()
{
char *p = foo();
printf("%s", p);
delete p;
}
且责任不对等:不满足谁分配谁回收的责任模式。
修改后:
char *foo()
{
char *p;
strcpy(p, "something");
return p;
}
void bar()
{
char *p = foo(p);
p = new char[10];
printf("%s", p);
delete p;
}
static
可以被private隐藏
与具体对象无关,可以直接用类访问
初始化一次,初始化时不需要写“static”
使用静态成员:
<class name>::<static member>
<object variable>.<static member>
静态成员变量
第一次执行函数时,静态变量会被初始化(只有一次)
void f() {
static int num_calls = 0;
...
num_calls++;
}
第一次调用时被初始化,以后每次调用都会加一。
静态对象变量
class X {
X(int, int);
~X();
...
};
void f() {
static X my_X(10, 20);
...
}
构造函数在第一次执行时调用,且只进行一次。
析构函数在退出程序时调用,最后构造的静态对象,最先析构。
void f(int x) {
if (x > 10) {
static X my_X(x, x * 21);
...
}
}
能跳过静态对象的构造。
与对象构造函数不能跳过不同。
全局对象全局变量
#include "X.h"
X global_x(12, 34);
X global_x2(8, 16);
main() is no longer the first function called
一个文件中静态变量的初始化按顺序进行,多文件时需要梳理文件之间的依赖关系。
- 尽量避免静态变量初始化之间的依赖。
- 一定要有依赖,最好把有依赖的静态变量按顺序放在一个文件里。
静态成员函数
只能访问静态成员变量。
也只有一次初始化的时机。
没有“this”指针,因为不属于任何对象,属于整个类。
只能访问静态成员变量。
不能被重写覆盖。
名字
用**“::”控制命名冲突的使用,“{}”**控制使用范围
class Marbles {
enum Colors { Blue, Red, Green };
...
};
class Candy {
enum Colors { Blue, Red, Green };
...
};
Marbles::Colors;
Candy::Colors;
namespace
// old1.h
void f();
void g();
// old2.h
void f();
void g();
// old1.h
namespace old1 {
void f();
void g();
}
// old2.h
Namespace old2 {
void f();
void g();
}
old1::f();
old2::f();
当命名空间名字一致时,编译器会合并同名的命名空间。
using
// Mylib.h
namespace MyLib {
void foo();
class Cat {
public:
void Meow();
};
}
// MyLib.cpp
#include ”MyLib.h”
void MyLib::foo() { cout << "foo\n"; }
void MyLib::Cat::Meow() {
cout << "meow\n";
}
//-----------------------------------------------------------------
// 节省工作量
void main() {
using MyLib::foo;
using MyLib::Cat;
foo();
Cat c;
c.Meow();
}
// -----------------------------------------------------------
void main() {
using namespace std;
using namespace MyLib;
foo();
Cat c;
c.Meow();
cout << “hello” << endl;
}
"using"有时会造成错误
// Mylib.h
namespace XLib {
void x();
void y();
}
namespace YLib {
void y();
void z();
}
void main() {
using namespace XLib;
using namespace YLib;
x(); // OK
y(); // Error: ambiguous
XLib::y(); // OK, resolves to XLib
z(); // OK
}
面对合并命名空间时的冲突,使用“using”指定
namespace first {
void x();
void y();
}
namespace second {
void y();
void z();
}
namespace mine {
using namespace first;
using namespace second;
using first::y(); // resolve clashes to first::x()
void mystuff();
...
}
using function
The derived class is able to “using” functions of its parent class
Inline
内联函数:在调用时展开,嵌入到代码中,避免调用开销,和宏类似。
int f(int i) {
return i*2;
}
main() {
int a = 4;
int b = f(a);
}
写成内联函数
inline int f(int i) {
return i * 2;
}
main() {
int a = 4;
int b = f(a);
}
等价于
main() {
int a = 4;
int b = a + a;
}
- 内联函数的实现必须在头文件里
- 内联是声明不是定义
坏处是会导致代码变大。不能滥用
宏和内联函数
#define f(a) (a)+(a)
main(){
double a = 4;
printf(“%d”, f(a));
}
宏是字符替换
inline int f(int i){
return i*2;
}
main(){
double a = 4;
printf(“%d”, f(a));
}
内联函数是嵌入
Inline may not in-line
函数过长参数过多或递归形式,编译器会忽略内联的关键字。
定义在类内的成员函数自动是内联函数。
class Cup {
int color;
public:
int getColor() { return color; }
void setColor(int color){
this->color = color;
} // 也可以写在函数外面,让接口看起来更加干净
};
Code-reuse
Composition
对象的组合
- Objects can be used to build up other objects
- Inclusion by reference allows sharing
It is the relationship of “has-a”
Inheritance
继承
回想钟表实现的例子,将公有特征抽象出来变成父类,子类继承后,不需要重复父类已经实现的功能。
分享共用
- Member data
- Member functions
- Interfaces
子类是父类的扩充
例子DoME
存储用户输入的CDs和DVDs的信息,一个简单的信息管理系统,允许简单的检索。
使用组合
可扩展性差,需要改动的位置多,最小化改动。
使用继承
It is the relationship of “is-a”
Public:使用继承时可见
Protect:继承时可见
Private:都不可见
class Employee {
public:
Employee( const std::string& name,const std::string& ssn );
const std::string& get_name() const;
void print(std::ostream& out) const;
void print(std::ostream& out, const std::string& msg) const;
protected:
std::string m_name;
std::string m_ssn;
};
Constructor for Employee
Employee::Employee( const string& name,
const string& ssn )
: m_name(name), m_ssn( ssn)
{
// initializer list sets up the values!
}
Employee member functions
inline const std::string& Employee::get_name() const
{
return m_name;
}
inline void Employee::print( std::ostream& out ) const
{
out << m_name << endl;
out << m_ssn << endl;
}
inline void Employee::print(std::ostream& out, const std::string& msg) const
{
out << msg << endl;
print(out);
}
Now add Manager
class Manager : public Employee {
public:
Manager(const std::string& name,
const std::string& ssn,
const std::string& title);
const std::string title_name() const;
const std::string& get_title() const;
void print(std::ostream& out) const;
private:
std::string m_title;
};
Base class is mentioned by class name
Manager::Manager( const string& name, const string&
ssn, const string& title = "" )
:Employee(name, ssn), m_title( title )
{
}
Manager member functions
inline void Manager::print( std::ostream& out ) const
{
Employee::print( out ); //call the base class print
out << m_title << endl;
}
inline const std::string& Manager::get_title() const
{
return m_title;
}
inline const std::string Manager::title_name() const
{
return string( m_title + ": " + m_name );
// access base m_name
}
Uses
int main () {
Employee bob( "Bob Jones", "555-44-0000" );
Manager bill( "Bill Smith", "666-55-1234", "ImportantPerson" );
string name = bill.get_name(); // okay Manager inherits Employee
//string title = bob.get_title(); // Error -- bob is an Employee!
cout << bill.title_name() << '\n' << endl;
bill.print(cout);
bob.print(cout);
bob.print(cout, "Employee:");
//bill.print(cout, "Employee:"); // Error hidden!
}
Polymorphism
向上转换,动态绑定
使用“virtual”触发
class XYPos {...}; // x.y point
class Shape{
public:
Shape();
virtual ~Shape();
virtual void render();
void move(const XYPos&);
virtual void resize();
protected:
XYPos center;
}
class Ellipse: public Shape{
public:
Ellipse(float maj, foat minr);
virtual void render(); // will define own
protected:
float major_axis, minor_axis;
};
class Circle: public Ellipse{
public:
Circle(float radius) : Ellipse(radius, radius) {}
virtual void render();
};
void render(Shape* p){
p->render(); // calls correct render function
} // for given Shape!
void func(){
Ellipse ell(10, 20);
ell.render();
Circle circ(40);
circ.render();
render(&ell);
render(&circ);
}
直接赋值
Ellipse elly(20F, 40F);
Circle circ(60F);
elly = circ;
父类 = 子类;\ 会把父类中不存在而子类存在的砍掉
指针
Ellipse* elly = new Ellipse(20F, 40F);
Circle* circ = new Cricle(60F);
elly = circ;
elly->render();// Circle::render()
能够调用到子类的
引用
void func(Ellipse& elly) {
elly.render();
}
Circle circ(60F);
func(circ);
能够调用到子类的
Virtual destructors 虚拟析构函数
根据虚拟表寻找合适的析构函数进行调用
Overriding 虚函数重载
class Base {
public:
virtual void func();
}
class Derived : public Base {
public:
virtual void func();
//overrides Base::func()
}
void Derived::func() {
cout << "In Derived::func!";
Base::func(); // call to base class
}
允许子类重载函数返回的值可以是父类返回值的子类。
class Expr{
public:
virtual Expr* newExpr();
virtual Expr& clone();
virtual Expr self();
};
class BinaryExpr: public Expr{
public:
virtual BinaryExpr* newExpr(); // ok
virtual BinaryExpr& clone(); // ok
virtual BinaryExpr self(); // Error!
};
同一个虚函数可以有不同的参数列表
class Base { public:
vritual void func();
vritual void func(int);
};
纯虚
class CDevice {
public:
virtual ~CDevice();
virtual int read(...) = 0;
virtual int write(...) = 0;
virtual int open(...) = 0;
virtual int close(...) = 0;
virtual int ioctl(...) = 0;
};
不能被实例化,不能用来定义对象。
定义了接口规范
STL 标准模板库
container 容器对象 collection 集合对象 的实现
实现了很多的数据结构和算法
可以提供很多存储对象的数据结构
containers
- Vector (expandable array)
- Deque (expandable array, expands at both
ends) - List (double-linked)
- Sets and Maps
顺序容器
vector: variable array
deque: dual-end queue
list: double-linked-list
forward_list: as it
array: as “array”
string: char. array
vector
#include <iostream>
#include <vector>
using namespace std;
int main( ) {
vector<int> x;
for (int a=0; a<1000; a++)
x.push_back(a);
vector<int>::iterator p;
for (p=x.begin(); p<x.end(); p++)
cout << *p << " ";
return 0;
}
generic classes 泛型类
list
#include <iostream>
using namespace std;
#include <list>
#include <string>
int main( ) {
list<string> s;
s.push_back("hello");
s.push_back("world");
s.push_front("tide");
s.push_front("crimson");
s.push_front("alabama");
list<string>::iterator p;
for (p=s.begin(); p!=s.end(); p++) // 注意结束循环条件
cout << *p << " ";
cout << endl;
}
map
存储一对变量,关键字和数据
#include <map>
#include <string>
using namespacce std;
int main(){
map<string, float> price;
price["snapple"] = 0.75;
price["coke"] = 0.50;
string item;
double total=0;
while ( cin >> item )
total += price[item];
}