C++Primer Plus学习笔记

函数模板

tmplate <typename Anytype>:建立一个模板,并将类型命名为AnyType。关键字template和typename是必需的,除非可用class代替typename。

template <typename AnyType>
void Swap(AnyType &a, Anytype &b)
{
    AnyType temp;
    temp = a;
    a = b;
    b = temp;
}

如果需要多个将同一种算法用于不同类型的函数,使用模板。

局限性:

假设定义如下结构:

sturct job
{
    char name[40];
    double salary;
    int floor;
};

假设只想交换salary和floor成员,而不交换name成员,上述模板不能实现。此时需要模板具体化,命令编译器创建特定的实例。

普通原型:void Swap(job &, job &);

模板原型:template <typename T>

                  void Swap(T &, T &);

显示具体化:template <> void Swap<job>(job &, job &);

如果有多个原型,编译器在选择原型时,非模板 优先于 现实具体化 优先于 模板版本。

单独编译

分为三部分:

1)头文件:包含结构声明和使用和谐结构的函数的原型

2)源代码文件:包含与结构有关的函数的代码

3)源代码文件:包含调用与结构相关的函数的代码

不要将函数的定义或变量声明放到头文件中。例如,如果在头文件中包含一个函数定义,然后在其他两个文件(属于同一个程序)中包含该头文件,则同一个程序中将包含同一个函数的两个定义,除非函数是内联的,否则将出错。

头文件中长包含的内容:

1)函数原型

2)使用#define或const定义的符号常量

3)结构声明

4)类声明

5)模板声明

6)内联函数

在同一个文件中只能将同一个头文件包含一次。

#ifndef COORDIN_H
//基于预处理编译指令#ifndef(if not defined)
...
/*代码片段意味着仅当以前没有使用预处理器编译指令#define 定义名称COORDIN_H_时,才处理#ifndef和#endif之间的语句*/
#endif

例:

//coordin.h
#ifndef COORDIN_H_
/*
编译器首次遇到该文件时,名称COORDIN_H_没有定义。这种情况下,编译器将查看#ifndef和#endif之间的内容(这正是我们希望的)。
如果在同一个文件中遇到其他包含coordin.h的代码,编译器将知道COORDIN_H_已经被定义了,从而调到#endif后面的一行上。
*/
#define COORDIN_H_

struct polar
{
    double distance;
    double angle;
};
struct rect
{
    double x;
    double y;
};

polar rect_to_polar(rect xyops);
void show_polar(polar dapos);

#endif

OOP 对象和类

OOP特性:

1)抽象

2)封装和数据隐藏

3)多态

4)继承

5)代码可重用性

通常将类定义放在头文件中,类方法的代码放在源代码文件中。

定义位于类生命中的函数都将自动成为内联函数。

类的构造函数和析构函数

程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能成功地将对象初始化。

类构造函数专门用于构造新对象,将值赋给数据成员。

Stock::stock(const string & co, long n, double pr)
{
    company = co;
    if( n < 0 )
    {
        std::cerr<<"Number of shares can't be negative "
                <<company<<" shares set to 0.\n";
        shares = 0;
    }
    else
        shares = n;
    share_val = pr;
    set_tot();
}

程序声明对象时,将自动调用构造函数。

第一种方法,显示调用构造函数:stock food = Stock("World Cabbage", 250, 1.25);

另一种方法,隐式调用构造函数:Stock grament("Furry Mason", 50, 2.5);

将构造函数与new一起使用:Stock *pstock = new Stock("Electroshock Games", 18, 19.0); 创建一个Stock对象,将其初始化为参数提供的值,并将该对象的地址赋给pstock指针。这种情况下,对象没有名称,但可以使用指针来管理该对象。

构造函数被用来创建对象,而不能通过对象来调用

默认构造函数:如果没有提供任何构造函数,则C++将自动提供默认构造函数,它是默认构造函数的隐式版本,不做任何工作

默认函数是在未提供显示初始值时,用来创建对象的构造函数。 用于如下声明: Stock fluffy_the_cat;  // 使用默认构造函数

默认构造函数可能如下:Stock::stock(){ }

定义默认构造函数的两种方式。

1)给已有构造函数的所有参数提供默认值:Stock(const string * co = "Error", int n = 0, double pr = 0.0);

2)通过函数重载定义另一个构造函数——一个没有参数的构造函数:Stock();

在设计类时,通常提供对所有类成员做隐式初始化的默认构造函数。

Stock::Stock()
{    
    company = "no name";
    shares = 0;
    share_val = 0.0;
    total_val = 0.0;
}

构造函数还可如下:

/*
TableTennisPlayer类记录会员的姓名以及是否有球桌。
*/
TableTennisPlayer::TableTennisPlayer (const string & fn, 
    const string & ln, bool ht) : firstname(fn),
        lastname(ln), hasTable(ht) {}
/*
与下面这样写效果一样
*/
TableTennisPlayer::TableTennisPlayer(const string & fn,
    const string & ln, bool ht)
{
    firstname = fn;
    lastname = ln;
    hasTable = ht;
}

隐式的调用默认构造函数时,不要使用圆括号。

Stock first("concret conglomerate");  //调用构造函数

Stock second();   //声明一个方法,second是一个返回Stock对象的函数

Stock third;    //调用默认构造函数

调用一个默认构造函数后,Stock stock;

使用显式构造函数 stock = Stock("Nifty Foods", 10, 50.0)对其成员赋值

析构函数:

对象过期时,程序将自动调用一个特殊的成员函数——析构函数。如果构造函数使用new来分配内存,则析构函数将使用delete来释放这些内存。

析构函数名称:在类名前面加上~

析构函数没有参数。Stock的析构函数原型:

Stock::~Stock()
{
}

如果程序员没有提供析构函数,编译器将隐式的声明一个默认的析构函数,并在发现导致对象被删除的代码后,提供默认析构函数的定义。

//stock10.h
#ifndef STOCK10_H_
#define STOCK!)_H_
#include <string>

class Stock
{
private:
    std::string company;
    long shares;
    double share_val;
    double total_val;
    void set_tot(){total_val = shares * share_val ; }
public:
    Stock(); //默认构造函数
    Stock(const std::string & co, long n = 0, double pr = 0.0);
    ~Stock();//析构函数
    void buy(long num, double price);
    void sell(long num, double price);
    void update(double price);
    void show();
};

#endif

保证函数不会修改调用对象:C++解决方法是将const关键字放在函数的括号后面。

声明:void show() const;

定义:void sotck::show() const{}

C++11允许将列表初始化语法用于类,只要提供与某个构造函数的参数列表匹配的内容,并用大括号将他们括起。

Stock hot_tip = {"Derivatives Plus Plus", 100, 45.0};
Stock jock {"Sport Age Storage, Inc"};
Stock temp();

this指针

this指针指向用来调用成员函数的对象(this被作为隐藏参数传递给方法)。

每个成员函数(包括构造函数和析构函数)都有一个this指针。this指针指向调用对象。如果方法需要引用整个调用对象,则可以使用表达式*this。在函数的括号后面使用const限定符将this限定为const,这样将不能使用this来修改对象的值。

对象数组

Stock mystuff[4];

当程序创建未被显式初始化的类对象时,总是调用默认构造函数。

运算符重载

格式:operatorop(argument-list)

例如:operator+():重载+运算符;operator*():重载*运算符

//mytime1.h -- Time class before operator overloading
#ifndef MYTIME1_H_
#define MYTIME1_H_

class Time
{
private:
    int hours;
    int minutes;
public:
    Time();
    Time(int h, int m = 0);
    void AddMin(int m);
    void AddHr(int h);
    void Reset(int h = 0, int m = 0);
    Time operator+(const Time & t) const;
    void show() const;
};
#endif
//mytime1.cpp -- implementing Time methods
#include <iostream>
#include "mytime1.h"

...

Time Tiem::operator+(const Time & t) const
{
    Time sum;
    sum.minutes = minutes + t.minutes;
    sum.hours = hours + t.hours + sum.minutes / 60;
    sum.minutes %= 60;
    return sum;
}

...

友元

通常,公有类方法提供唯一的对类私有部分访问途径,但C++提供了另一种形式的访问权限:友元。

友元有三种:

1)友元函数

2)友元类

3)友元成员函数

通过让函数成为类的友元,可以赋予该函数与类的成员函数相同的访问权限。

创建友元:创建友元函数的第一步是将其原型放在类声明中,并在原型声明前加上关键字friend

friend Time operator*(double m, const Time & t);

该原型意味着:1.虽然operator*()函数是在类声明中声明的,但他不是成员函数,因此不能使用成员运算符来调用;2.虽然operator*()函数不是成员函数,但它与成员函数的访问权限相同。

第二步:编写函数定义。因为它不是成员函数,所以不要使用Time::限定符。另外,不要在定义中使用关键字friend

定义如下:

Time operator*(double m, const Time & t)
{
    Time result;
    long totalminutes = t.hours * m *60 + t.minutes * m;
    result.hours = totalminutes / 60;
    result.minutes = totalminutes % 60;
    return result;
}

类继承

能够从已有的类派生出新的类,而派生类继承了原有类(基类)的特征,包括方法。

可通过继承完成的工作:

1)可以在已有的类上添加新功能

2)可以给类添加数据

3)可以修改类的方法

一个简单的基类:

class TableTennisPlayer
{
private:
    string firstname;
    string lastname;
    bool hasTable;
public:
    TableTennisPlayer(const string & fn = "none", const string & ln = "none", bool ht = false);
    void Name() const;
    bool HasTable() const {return hasTable;}
    void ResetTable(bool v){ hasTable = v;}
};

派生一个类:

class RatedPlayer : public TableTennisPlayer
{
... 
};

冒号支出RatedPlayer类的基类是TableTennisPlayer类。上述特殊的声明头表明TableTennisPlayer是一个公有基类,这称为公有派生。

使用公有派生,基类的公有成员将称为派生类的公有成员;基类的私有部分也将成为派生类的一部分,但只能通过基类的公有和保护方法访问。

派生类需要自己的构造函数;派生类可以根据需要添加额外的数据成员和成员函数

下面例子,派生类需要另一个数据成员来存储比分,还应包含检索比分的方法和重置比分的方法。因此,类声明如下:

class RatedPlayer : public TableTennisPlayer
{
private:
    unsigned int rating;
public:
    RatedPlayer (unsigned int r = 0, const string & fn = "none", const string & ln = "none", bool ht = false);
    RatedPlayer(unsigned int r, const TableTennisPlayer & tp);
    unsigned int Rating() const { return rating; }
    void ResetRating ( unsigned int r) { rating = r; }
};

构造函数必须给新成员(如果有的话)和继承的成员提供数据。在第一个RatedPlayer构造函数中,每个成员对应一个形参;第二个RatedPlayer构造函数使用一个TableTennisPlayer参数,该参数包括firstname, lastname, hasTable

派生类不能直接访问基类的私有成员,而必须通过基类方法进行访问。派生类构造函数必须使用基类构造函数。

创建派生类对象时,程序首先创建基类对象。下面是第一个RatedPlayer构造函数代码:

RatedPlayer::RatedPlayer(unsigned int r, const string & fn, 
    const string & ln, bool ht) : TableTennisPlayer(fn, ln, ht)
{
    rating = r;
}

派生类和基类的关系:1.派生类对象可以使用基类的方法,条件是方法不是私有的。2.基类指针可以在不进行显式类型转换的情况下指向派生类对象;基类引用可以在不进行显示类型转换的情况下引用派生类对象。3.基类指针或引用只能用于调用基类方法。

派生类不能直接访问基类的私有成员,必须使用基类的公有方法才能访问这些数据。访问的方式取决于方法。构造函数使用一种技术,而其他成员函数使用另一种技术。

构造函数:

//第一种方法
BrassPlus::BrassPlus( const string & s, long an, double bal,
            double ml, double r ) : Brass(s, an ,bal)
{
    maxLoan = ml;
    oweBank = 0.0;
    rate = r;
}
//第二种方法
BrassPlus::BrassPlus(const Brass & ba, double ml, double r)
            : Brass(ba) // use implicit copy constructor
{
    maxLoan = ml;
    oweBank = 0.0;
    rate = r;
}

继承:is-a关系(公有继承)

3种继承方式:公有继承、保护继承和私有继承

公有继承:is-a关系,即派生类对象也是一个基类对象,可以对基类对象执行的任何操作,也可以对派生类对象执行。继承可以在积累的基础上添加属性,但不能删除积累的属性。

is-a关系:例如:假设有一个Fruit类,可以保存水果的重量和热量。因为香蕉是一种特殊的水果,所以可以从Fruit类派生出Banana类。

多态公有继承:

多态:希望同一个方法在派生类和基类中的行为是不同的。方法的行为应取决于调用该方法的对象。

两种重要的机制可用于实现多态公有继承:

1)在派生类中重新定义基类方法

2)使用虚方法

使用virtual:如果方法通过引用或指针而不是对象调用,它将确定使用哪一种方法。如果没有使用关键字virtual,程序将根据引用类型或指针类型选择方法;如果使用了virtual,程序将根据引用或指针只想的对象的类型来选择方法。virtual只用于类声明的方法原型中。不用于方法定义中。

经常在基类中将派生类会重新定义的方法生命为虚方法。为基类声明一个虚析构函数也是一种惯例。这样做是为了确保释放派生对象时,按正确的顺序调用析构函数。

访问控制:protected

protect与private相似,在类外只能用公有类成员来访问protected部分中的类成员。protected和private之间的区别只有在基类派生的类中才会表现出来。派生类的成员可以直接访问基类的保护成员,但不能直接访问基类的私有成员。因此,对于外部世界来说,保护成员的行为与私有成员相似;但对于派生类来说,保护成员的行为与公有成员相似。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值