两个小时重温C++

面向对象的思想

封装

封装意味着把对象的属性和方法结合成一个独立的系统单位,并尽可能隐藏对象的内部细节。

比如一个手电筒,我们不要关心它的内部实现机制,它的电路等等,我们只要了解开关(输入)和灯的亮度(输出)。

抽象

抽象的过程是对具体问题进行概括的过程,是对一类公共问题进行统一描述的过程。

为了使某些必要的信息得以顺利的交流,设计者必须制定一个抽象,就如同一个协议,一个得到所有参与活动的有效个体支持的协议。

比如某些父类的方法定义为virtual,那么具体实现就在子类中实现。

继承

子类对象拥有与其基类相同的全部属性和方法,称为继承。

多继承:有一部分学生还教课挣钱(助教),该怎么办?存在了既是老师又是学生的复杂关系,也就是同时存在着两个”是一个”关系。

我们需要写一个 TeschingStudent 类让它同时继承 Teacher 类和 Student 类,换句话说,就是需要使用多继承。

基本语法:

class TeachingStudent : public Student, public Teacher
{
    …
}

虚继承:C++ 发明者也想到了这部分的冲突,因此为此提供了一个功能可以解决这个问题:虚继承(virtual inheritance)通过虚继承某个基类,就是在告诉编译器:从当前这个类再派生出来的子类只能拥有那个基类的一个实例。

虚继承的语法:

class Teacher : virtual public Person
{
    …
}

这样做我们的问题就解决了:让 Student 和 Teacher 类都虚继承自 Person 类,编译器将确保从 Student 和 Teacher 类再派生出来的子类只能拥有一份 Person 类的属性!

多态

多态是指在基类中定义的属性和行为被子类继承后,可以具有不同的数据类型或者表现行为等特性。比如基类实现了方法A,然后子类实现了方法A。当声明一个基类但是使用子类进行实例化的话,那么该声明的基类使用的是子类的方法A。

多态性是面向对象程序设计的重要特征之一。
简单的说,多态性是指用一个名字定义不同的函数,调用同一个名字的函数,却执行不同的操作,从而实现传说中的”一个接口,多种方法”!

多态是如何实现绑定的?

编译时的多态性:通过重载实现
运行时的多态性:通过虚函数实现
编译时的多态性特点是运行速度快,运行时的多态性特点是高度灵活和抽象。

重载
所谓函数重载的实质就是用同样的名字再定义一个有着不同参数但有着同样用途的函数。

运算符重载:

重载运算符的函数一般格式如下:

函数类型 operator 运算符名称(形参表列)
{
    对运算符的重载处理
}

例如我们可以重载运算符 + , 如下:

int operator+(int a, int b)
{
    return (a – b);
}

举个栗子:实现复数加法
(3, 4i)+ (5, -10i)= (8, -6i)

可以进行重载的运算符:

其实,我们还可以对运算符重载函数 operator+ 改写得更简练一些:

Complex Complex::operator+(Complex &c2)
{
    return Complex(real+c2.real, imag+c2.imag);
}

允许重载的符号有:

.(成员访问运算符)
.*(成员指针访问运算符)
::(域运算符)
sizeof(尺寸运算符)
?:(条件运算符)

重载不能改变运算符运算对象(操作数)个数。
重载不能改变运算符的优先级别。
重载不能改变运算符的结合性。
重载运算符的函数不能有默认的参数

运算符重载函数作为类友元函数

不知道刚刚有没有鱼油有这样的疑问:”+”运算符是双目运算符,为什么刚刚的例子中的重载函数只有一个参数呢?

解答:实际上,运算符重载函数有两个参数,但由于重载函数是 Complex 类中的成员函数,有一个参数是隐含着的,运算符函数是用 this 指针隐式地访问类对象的成员。

return Complex(real+c2.real, imag+c2.imag);
return Complex(this->real+c2.real, this->imag+c2.imag);
return Complex(c1.real+c2.real, c1.imag+c2.imag);

输入输出流

cout是输出流,是console out,将数据输出到屏幕上。这里的符号<< 是左移符号进行了重载。这个流对象是ostream。

cin是输入流,这个流的对象是istream。cin >> 就是从istream流对象中提取所需要的数据。

在while( cin >> i ) 中,表达式 cin >> i 返回输入流对象本身,也就是cin。但是,如果到达了文件尾或者提取操作符遇到一个非法值,这个返回值将是 false。注意,在while( cin >> i ) 中,当用户在键盘上点击“enter”键的时候,在这一句并不会结束。

1:cin.ignore()和cin.getline()
2:cin.get()和cin.peek()
3:cin.gcount()和cin.read()

1:cout.precision()
2:cout.width()

对于cin的一些状态表示:

  • eof():如果到达文件(或输入)末尾,返回true;
  • fail():如果cin 无法工作,返回true;
  • bad():如果cin 因为比较严重的原因(例如内存不足)而无法工作,返回true;
  • good():如果以上情况都没发生,返回true。

argc 和 argv
argc是表示输入函数参数的个数,argc的含义是程序的参数数量,包含本身。
argv[]的每个指针指向命令行的一个字符串,所以argv[0]指向字符串”copyFile.exe”。是输入的具体参数。

ifstream和ofstream
ifstream in; in.open( “test.txt” );
ofstream out; out.open( “test.txt” );

它们都是用一个open 函数来完成打开文件的功能。当然,这不是唯一的方法,我们还可以这样实现。
ifstream in( “test.txt” ); 和 ofstream out( “test.txt” );

以上代码在创建一个ifstream 和ofstream 类的对象时,将文件的名字传递给它们的构造函数。
暂时我们可以这么理解构造函数:就是对象默认使用的函数(方法)。两者是没有区别。

除此之外,ifstream in( char* filename, int open_mode)
其中,filename 表示文件的名称,它是一个字符串; open_mode 表示打开模式,其值用来定义以怎样的方式打开文件(跟open的参数一样)。

下面给出几种常见的打开模式:
ios::in — 打开一个可读取文件
ios::out — 打开一个可写入文件
ios::binary — 以二进制的形式打开一个文件。
ios::app — 写入的所有数据将被追加到文件的末尾
ios::trunk — 删除文件原来已存在的内容
ios::nocreate — 如果要打开的文件并不存在,那么以此参数调用open 函数将无法进行。
ios::noreplece — 如果要打开的文件已存在,试图用open 函数打开时将返回一个错误。

指针
引用(&)和解引用(*)。

引用就是取地址,解引用就是取地址的值。

  • C++ 允许指针群 P,就是多个指针有同样的值
    int *p1 = &myInt;
    int *p2 = &myInt;
  • C++ 支持无类型(void)指针,就是没有被声明为某种特定类型的指针,例如:
    void *vPointer;

结构体指针
第一种:

创建一个FishOil类型的变量:
FishOil Jiayu = { “小鱼”, “fishc_00000”, ‘M’ }

创建一个指向该结构的指针:
FishOil *pJiayu = &Jiayu;
注意:因为指针的类型必须与指向的地址的变量的类型一致,所以pJiayu指针的类型也是FishOil

我们可以通过对指针进行解引用来访问相应的变量值
(*pJiayu).name = “黑夜”;
(*pJiayu).id = “fishc_00001”;

如果你觉得刚刚的方法不够地道不够味儿,可以换用第二种方法:
i.e. …… ……
pJiayu -> name = “黑夜”;
pJiayu -> id = “fishc_00001”;
pJiayu -> sex = F;
std::cout << pJiayu -> name;
std::cout << pJiayu -> id;
std::cout << pJiayu -> sex;

传值、传址和传引用
在函数调用的时候,传递的是值,地址和引用。

传递的引用一般是在函数参数处加&。

传地址就显示地传递地址。

联合、枚举、别名
union是一个空间可以翻译成不同的数据,枚举是使用一些比较容易记住的名字代表常量,别名就是使用typedef进行对某些比较长或者比较难记的东西进行使用别的简短的代号。

类里static用来声明初始化常量
C++允许在类里声明常量,但不允许对它进行赋值

abstract method
在父类中对抽象方法不具体化,只有在子类中才会具体化。这也是多态的一种表现形式。

class Car
{
    public:
        const float TANKSIZE = 85; // 出错@_@
}

绕开这一限制的方法就是创建一个静态常量

class Car
{
    public:
        static const float FULL_GAS = 85;
}

构造与析构
构造函数用于初始化,析构函数在对象作用域结束的时候被调用,用来清除之前构造函数开辟的内存。


class Car
{
    Car(void);
    ~Car();
}

其次,析构器也永远不返回任何值。

另外,析构器是不带参数的。所以析构器的声明永远是如下格式:~ClassName();
在我们刚刚的例子中析构器可有可无。但是在比较复杂的类里,析构器往往至关重要(可能引起内存泄露)。例如某个类的构造器申请了一块内存,我们就必须在析构器里释放那块内存。

继承中的构造和析构函数
继承中的构造函数是从父类开始,再到子类。析构函数正好顺序相反。

Animal::Animal( std::string theName )
{
    name = theName;
}

Pig::Pig( std::string theName ) : Animal( theName )
{
}

注意在子类的构造器定义里的”:Animal(theName)”语法含义是:

当调用 Pig() 构造器时(以 theName 作为输入参数),Animal()构造器也将被调用( theName 输入参数将传递给它)。

于是,当我们调用 Pig pig(“小猪猪”); 将把字符串 “小猪猪” 传递给 Pig() 和 Animal(),赋值动作将实际发生在 Animal() 方法里。

访问控制
public、protect、private。

可以看【2】中的描述。

友元
当两个类的变量需要访问,但是不想让外部访问的时候,可以引入友元,friend class xx。这样的话,A就可以访问B的private和protected成员和函数了。

静态成员和静态方法
脱离于对象依赖于类而存在。

静态方法不能使用private成员。

静态方法也可以使用一个普通方法的调用语法来调用,但建议不要这么做,那会让代码变得更糟糕!
请坚持使用:ClassName::methodName();

虚函数
A* a = new B;

那么如果B是A的子类,那么如果没有申明虚函数,那么调用方法的时候就是A的方法,如果有虚函数,调用的方法就是B的方法。参考链接:【3】【4】

assert

这个函数是在C语言的 assert.h 库文件里定义的,所以包含到C++程序里我们用以下语句:#include <cassert>

assert()函数需要有一个参数,它将测试这个输入参数的真 or 假状态。
如果为真,Do nothing!
如果为假,Do something!

catch,throw

基本使用思路:
1. 安排一些C++代码(try语句)去尝试某件事 —— 尤其是那些可能会失败的事(比如打开一个文件或申请一些内存)
2. 如果发生问题,就抛出一个异常(throm语句)
3. 再安排一些代码(catch语句)去捕获这个异常并进行相应的处理。

捕获异常的基本语法如下:
try
{
    // Do something.
    // Throw an exception on error.
}
catch
{
    // Do whatever.
}

动态内存
如果没有足够的可用内存空间?那么 new 语句将抛出 std::bad_alloc 异常!

注意在用完内存块之后,应该用 delete 语句把它还给内存池。

另外作为一种附加的保险措施,在释放了内存块之后还应该把与之关联的指针设置为NULL。

函数调用之后返回new的动态内存的地址:在函数里调用 new 语句为某种对象或某种基本数据类型分配一块内存,再把那块内存的地址返回给程序的主代码,主代码将使用那块内存并在完成有关操作后立刻释放。

但是对于不是动态分配的内存区域,不应该让函数返回一个指向局部变量的指针函数或方法有它们自己的变量,这些变量只能在这个函数的内部使用,这些变量我们成为局部变量(local variable)。我们又知道如何利用指针在某个函数内部改变另一个函数的局部变量的值(例如传址调用)。

指针函数和函数指针
函数指针:指向函数首地址的指针变量称为函数指针(栗子)。

指针函数:一个函数可以带回一个整型数据的值,字符类型值和实型类型的值,还可以带回指针类型的数据,使其指向某个地址单元。

头文件的引用
在创建了头文件之后,只要把它的文件名用双引号括起来写在如下所示的指令里就可以导入它:

#include “fishc.h”

如果没有给出路径名,编译器将到当前子目录以及当前开发环境中的其他逻辑子目录里去寻找头文件。
为了消除这种猜测,在导入自己的头文件时可以使用相对路径。如果头文件与主程序文件在同一个子目录里,则可以这么写:

#include “./fishc.h”

如果头文件位于某个下级子目录里,那么以下级子目录的名字开头:

#include “includes/fishc.h”

最后,如果头文件位于某个与当前子目录平行的”兄弟”子目录里,则需要这么写:

#include “../includes/fishc.h”

宏定义

#if 0
// 这里有代码
// 这里有好多代码
// 这里有好多好多代码
// 这里有好多好多好多代码
#endif

#ifndef LOVE_FISHC
#define LOVE_FISHC
#endif

这看起来好像没什么用,但事实却并非如此。这段代码的含义是:如果LOVE_FISHC还没有定义则定义之,看出这有什么作用了吗?

#ifndef LOVE_FISHC
#define LOVE_FISHC
class Rational{ … };
#endif

这里需要注意的就是ifdef和undef是为了防止把一个文件重复引用。

命名空间
第一种方法我们已经用了很多遍了:
std::cout << “I love fishc.com!n”;

第二种方法是使用using指令:
using namespace std;

执行这条语句后,在std命名空间里定义的所有东西就都可以使用,我们便可以像下面直接使用:
cout << “I love fishc.com”

不过,把命名空间里的东西带到全局作用域里,跟我们使用命名空间的本意相违背!
所以,不建议在文件开头直接用using namespace XX这种设计风格。

最后一种方法是用一个using指令只把你需要的特定命名从命名空间提取到全局作用域:

using std::cout;
cout << “I love fishc.com!n”;

存储类
每个变量都有一个存储类,它决定着程序将把变量的值存储在计算机上的神马地方、如何存储,以及变量应该有着怎样的作用域。

默认的存储类是auto(自动),但你不会经常看到这个关键字,因为它是默认的,阴魂不散的!

自动变量存储在称为栈(stack)的临时内存里并有着最小的作用域,当程序执行到语句块或函数末尾的右花括号时,它们将被系统回收(栈回收),不复存在。
与auto不同的是static,static变量在程序的生命期内将一直保有它的值而不会消亡,因为它们是存储在静态存储区,生命周期为从申请到程序退出(和全局变量一样)。

另外我们稍后就会提到的,一个static变量可以有external或internal链接。
第三种存储类是extern,它在有多个翻译单元时非常重要。这个关键字用来把另一个翻译单元里的某个变量声明为本翻译单元里的一个同名全局变量。
注意,编译器不会为extern变量分配内存,因为在其他地方已经为它分配过内存。

用extern关键字相当于告诉编译器:“请相信我,我发誓我知道这个变量在其他翻译单元里肯定存在,它只是没在这个文件里声明而已!”

还有一个存储类是register,它要求编译器把一个变量存储在CPU的寄存器里。但有着与自动变量相同的作用域。register变量存储速度最快,但有些编译器可能不允许使用这类变量。

链接
外链接的意思是每个翻译单元都可以访问这个东西(前提是只要它知道有这么个东西存在)。
普通的函数、变量、模板和命名空间都有外链接。extern

// this.cpp
int i1 = 1;

// that.cpp
extern int i1;
int i2 = i1;

内链接的含义是:在某个翻译单元里定义的东西只能在翻译单元里使用,在任何函数以外定义的静态变量都有内链接:static

// this.cpp
static int d = 8;

// that.cpp
static int d = 9

在函数里定义的变量只存在于该函数的内部,根本没有任何链接(none)。

泛型编程
使用模板template可以对不同类型的数据进行同样的操作。

template <class T>
void foo(T param)
{
    // do something
}

可以用template 来代替template ,它们的含义是一样。

类模板:

template <class T>
class MyClass
{
    MyClass();
    void swap(T &a, T &b);
}

构造函数是:

MyClass<T>::MyClass()
{
    // 初始化操作。
}

C++并没有限制只能使用一个类型占位符,如果类模板需要一种以上的类型,根据具体情况多使用几个占位符即可。

template <class T, class U>
class MyClass
{
    // … …
}

容器
能容纳两个或更多个值的数据结构通常我们称为容器(container)。

将某些数据结构进行规整化,使它可以处理不同的数据。

迭代器就是遍历容器的智能指针。

参考链接:
【1】数据结构与算法: http://blog.fishc.com/category/cpp/page/5
【2】访问控制:http://www.cnblogs.com/BeyondAnyTime/archive/2012/05/23/2514964.html
【3】虚函数1:http://www.cnblogs.com/malecrab/p/5572730.html
【4】虚函数2:http://www.cnblogs.com/hitwtx/archive/2011/08/20/2147431.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值