《数据结构与算法分析–C++语言描述》(第四版) 第1章 程序设计:综述

第1章程序设计:综述

1.3 递归简论

一个用其自身来定义的函数就称为递归(recursive)的
	递归必须有基准情形,否则毫无意义
	递归不是循环推理:虽然定义一个递归函数用的是这个函数本身,但是我们并没有用函数本身定义该函数的一个特定实例(基准情形)
	跟踪挂起的函数调用(这些函数调用已经开始但是正等待着递归调用来完成)以及它们的变量的记录工作都是由计算机自动完成的
	运行时:递归调用将持续进行直到基准情形出现
	递归绝对不应该作为简单for循环的替代物
递归的4条基本法则:
	1.基础情形。必须总要有某些基准情形,它无须递归就能解出。
	2.要有进展。对于那些需要递归求解的情形,每一次递归调用都必须要使状况朝向一种基准情形推进。
	3.设计法则。假设所有的递归调用都能运行。
	4.合成效益法则。求解一个问题的同一实例时,切勿在不同的递归调用中做重复性的工作
注意:
	使用递归计算诸如斐波那契数之类简单数学函数的值的思路一般不是一个好主意,其道理正是根据第4条法则。

一个终止函数
01_1.3_fig1.2_P7.cpp

// fig1.2_P7.cpp

#include
using namespace std;
int f(int x)
{
    if(x==0)
        return 0;
    else
        return (2*f(x-1)+x*x);
}
int main()
{
    cout<<"f(5)="<<f(5)<<endl;
}

无终止递归函数
01_1.3_fig1.3_P8.cpp

// fig1.3_P8.cpp 

#include 
using namespace std;

int bad( int n )
{
    if( n == 0 )
        return 0;
    else
        return bad( n / 3 + 1 ) + n - 1;//bad(1)定义bad(1),造成死循环
}

int main( )
{
    cout << "bad is infinite recursion" << endl;
    return 0;
}

打印整数的递归例程
01_1.3_fig1.4_P9.cpp

// fig1.4_P9.cpp 

//####################打印一个正整数n########################
//要求:正常顺序显示,即先打印最高位再打印次高位
//分析
//一位数:判断出来是一位数,直接打印
//两位数:先打印十位、再打印个位(一位数:)
//三位数:先打印千位、再打印下面两位(两位数:)
//...
//可以看出不论是几位数,函数内部形式相同,可以用小的定义大的--->递归函数
#include 
using namespace std;
void printDigit(int OnePsitiveInteger) //打印一位正整数
{
    cout << OnePsitiveInteger;
}
void printOut(int n) // Print nonnegative n //for example output 90
{
    if (n >= 10)          //先判断是不是多位数,一层层的递归,找到最高位那个数
        printOut(n / 10); //printOut(9)-->printDigit(9%10)-->9
    printDigit(n % 10);   //printDigit(0%10)-->0
}
int main()
{
    printOut(12345678);
    getchar();
    return 0;
}

1.4 C++类

1.4.1 基本的class语法

一个IntCell类的完整声明
01_1.4.1_fig1.5_P11.cpp

// fig1.5_P11.cpp

#include 
using namespace std;

//A class for simulating an integer memory cell
//一个模拟整数内存单元的类
class IntCell
{
public:
    //Construct the IntCell
    //Initial value is 0
    IntCell()
    {
        storeValue = 0;
    }

    //Construct the IntCell
    //Initial value is initialValue
    IntCell(int initialValue)
    {
        storeValue = initialValue;
    }

    //Return the storeValue
    int read()
    {
        return storeValue;
    }

    //Change the storedvalue to x
    void write(int x)
    {
        storeValue = x;
    }

private:
    int storeValue;
};

int main()
{
    IntCell m;
    m.write(5);
    cout<<"Cell contents: "<<m.read()<<endl;
}

1.4.2 构造函数的访问语法和访问函数

经过修订过的IntCell类
01_1.4.2_fig1.6_P12.cpp

// fig1.6_P12.cpp

#include 
using namespace std;

//A class for simulating an integer memory cell
class IntCell
{
public:
    //初始化列表中使用{},而不是传统的(),是C++11新规定的
    explicit IntCell(int initialValue = 0) : storeValue{initialValue} {}

    //常成员函数
    int read() const
    {
        return storeValue;
    }
    void write(int x)
    {
        storeValue = x;
    }

private:
    int storeValue;
};
int main()
{
    IntCell m;
    IntCell m2{};//在对单参数构造函数使用了explicit之后,想使用默认参数时,C++11中可以这样写(推荐)
    m.write(5555);
    cout << "m  Cell contents: " << m.read() << endl;
    cout << "m2 Cell contents: " << m2.read() << endl;
    return 0;
}
  • 初始化列表:分三种情况,其中前两种情况必须使用初始化列表
  1. 数据成员为const类型(即当对象被创建之后该const数据成员将不可改变),那么该数据成员的值只能在初始化列表中被初始化
  2. 如果数据成员本身是类类型,该类型又没有0作为参数的构造函数,那么这个成员必须在初始化列表中被初始化
  3. 基本数据类型,使用初始化列表,可以节约时间
  • explicit构造函数
    应该使所有的单参数构造函数为explicit的
    因为:单参数构造函数定义了一个隐式类型转换,应该避免这种情况的出现

  • 常成员函数

    • 一个检查但不改变其对象状态的成员函数叫做访问函数(accessor)
      如何使成员函数成为访问函数:在终止参数类型表列的封闭圆括号的后面加上关键字const
      这种常态性是特征的一部分
      只有const用于封闭的圆括号之后才意味着函数是个访问函数
    • 改变其对象状态的成员函数叫做修改函数(mutator)
      修改函数不能用于常对象上

1.4.3 接口与实现的分离

fig1.6==fig1.7+fig1.8+fig1.9(拆分为头文件.h 源文件.cpp main.cpp)

文件IntCell.h中的IntCell类接口
01_1.4.3_fig1.7_P14IntCell.h

// fig1.7_P14IntCell.h 

#ifndef __fig1_7_P14IntCell_h__
#define __fig1_7_P14IntCell_h__
class IntCell
{
public:
    explicit IntCell(int initialValue = 0);
    int read() const;
    void write(int x);

private:
    int storeValue;
};
#endif

文件IntCell.cpp中的IntCell类实现
01_1.4.3_fig1.7_P14IntCell.h

// fig1.8_P14IntCell.cpp

#include "fig1.7_P14IntCell.h"
IntCell::IntCell(int initialValue) : storeValue{initialValue} {}
int IntCell::read() const
{
    return storeValue;
}
void IntCell::write(int x)
{
    storeValue=x;
}

文件TestIntCell.cpp中用到的IntCell的程序
01_1.4.3_fig1.7_P14TestIntCell.cpp

// fig1.9_P15TestIntCell.cpp

#include
#include"fig1.7_P14IntCell.h"
using namespace std;

int main()
{
    IntCell m;
    m.write(55);
    cout<<"Cell contents: "<<m.read()<<endl;
    return 0;
}

ClassName::member ,符号::称作作用域解析运算符(scope resolution operator)
成员函数的实现 必须和 成员函数的声明 的特征完全一致
默认参数只在声明处指定,在实现处是省略的

1.4.4 vector类和string类

使用vector类:存储100个平方数并输出
01_1.4.4_fig1.10_P16.cpp

// fig1.10_P16.cpp

#include
#include
using namespace std;

int main()
{
    vector<int> squares(100);
    for(int i=0;i<squares.size();++i)
    {
        squares[i]=i*i;
    }
    for(int i=0;i<squares.size();++i)
    {
        cout<<i<<" "<<squares[i]<<endl;
    }

}
vector<int> tempa(12);//vector数组的大小是12
vector<int> tempb{12};//vector数组的大小是1,这个唯一的元素 tempb[0]=12
范围for循环(range for)
	适用场景:像数组或vector这样的集合,需要依序访问,并且不需要使用数组元素的下标的时候
	迄今为止范围for循环只允许对项的查看
	例子:fig1.10
		int sum=0;
		for(int x:squares)//x的类型,和squares中元素的类型一致;当然使用auto自动推导也可以
		{
			sum+=squares;
		}
		//使用auto,自动推导x的类型
		int sum=0;
		for(int x:squares)
		{
			sum+=squares;
		}

1.5 C++细节

1.5.1 指针(pointer)

指针变量(pointer variable)
	1.它是一种变量
	2.存放另一对象所占用的地址

使用指向IntCell的指针的程序(只是举例)

// fig1.11_P18.cpp

#include 
#include "fig1.7_P14IntCell.h"
using namespace std;
int main()
{
    IntCell *m = new IntCell{0};
    m->write(12345);
    cout << "Cell contents: " << m->read() << endl;

    if (m != nullptr)
    {
        delete m;
        m = nullptr;
        if (m != nullptr)
        {
            delete m;
            m = nullptr;
        }
    }
    return 0;
}

1.5.2 左值、右值和引用

左值
	是一个标识非临时性对象的表达式
	如果程序中有一个变量名,那么它就是一个左值,而不管该变量是否可被修改
右值
	是一个标识临时性对象的 表达式,或者是一个不与任何对象相联系的值(如字面值常数)

引用类型允许我们为一个已存在的值定义一个新的名字
	对引用取地址==对原变量取地址
左值引用(lvalue reference)
	左值引用的声明是通过在某个类型后放置一个符号'&'来进行的
	此时,一个左值引用成为了它所引用对象的同义词(另一个名字)
右值引用(rvalue reference)
	右值引用是通过在某个类型后放置一个符号'&&'而被声明的
	右值引用与左值引用具有相同的特征,不过有一点不同于左值引用的是,右值引用也可以引用一个右值(即一个临时变量)
左值引用的用途
	1.给结构复杂的名称起别名
	2.范围for循环
		比如让一个vector对象的所有值增加1
		for(auto & x :arr)//若是 'auto x : arr'是行不通的,因为x只是vector中每一个值的拷贝
		{
			++x;
		}
	3.避免复制/拷贝
		函数参数:传引用
		函数返回值:返回引用

1.5.3 参数传递

传值还是传引用
	1.形参会改变实参的值:传引用
	2.形参不改变实参的值:传常量引用(const& 之前)
	3.实参很小(基本数据类型),实参不被形参改变:传值是可以的

1.5.5 std::swap和std::move

如果对象有名字,那么它就是一个左值
std::move函数,它能够把任何左值(或右值)转换成右值
	std::move函数,并不移动数据,确切地说,它使一个值易于移动
C++11中,如果赋值运算符的右边(或构造函数)是一个右值,那么当对象支持移动操作时我们能够自动地避免复制

使用std::move函数的例子//通过3次移动进行交换
void swap(vector<string> &x, vector<string> &y)
{
    vector<string> tmp = std::move(x);
    x = std::move(y);
    y = std::move(tmp);
}

1.5.6 五大函数:析构函数、拷贝构造 移动构造、拷贝赋值 移动赋值

fig1.18 数据成员是指针,写出五大函数
fig1.18_P29.cpp

#include 
using namespace std;

//figure 1.18 Implements the big five
class IntCell
{
public:
    explicit IntCell(int initialValue = 0)
    {
        storeValue = new int{initialValue};
    }

    ~IntCell()
    {
        delete storeValue;
    }

    //拷贝构造
    IntCell(const IntCell &rhs)
    {
        storeValue = new int{*rhs.storeValue};
    }
    //移动构造
    IntCell(IntCell &&rhs) : storeValue{rhs.storeValue}
    {
        rhs.storeValue = nullptr;
    }

    //拷贝赋值
    IntCell &operator=(const IntCell &rhs)
    {
        if (this != &rhs)
            *storeValue = *rhs.storeValue;
        return *this;
    }
    //移动赋值
    IntCell &operator=(IntCell &&rhs)
    {
        std::swap(storeValue, rhs.storeValue);
        return *this;
    }

    int read() const
    {
        return *storeValue;
    }
    void write(int x)
    {
        *storeValue = x;
    }

private:
    int *storeValue;
};
void myswap(IntCell &lhs, IntCell &rhs)
{
    IntCell tmp = static_cast<IntCell &&>(lhs); //std::move
    lhs = static_cast<IntCell &&>(rhs);
    rhs = static_cast<IntCell &&>(tmp);
}

//figure 1.5
int f()
{
    IntCell a(2);
    IntCell b = a;
    IntCell c;

    c = b;
    a.write(4);
    myswap(a, b);
    cout << a.read() << endl
         << b.read() << endl
         << c.read() << endl;
    return 0;
}
int main()
{
    f();
    return 0;
}

1.6 模板

1.6.2 类模板

MemoryCell类模板
fig1.21_P32.cpp

#include 
#include 
using namespace std;

//A class for simulating a memory cell.
//类模板
template <typename Object>
class MemoryCell
{
public:
    //默认参数使用 零/无参数构造函数构造一个object对象 然后Object对象用来初始化此新对象
    explicit MemoryCell(const Object &initialValue = Object{})
        : storeValue{initialValue} {} //千万注意中英文区别已经大小写,我就栽在了':'
    const Object &read() const
    {
        return storeValue;
    }
    void write(const Object &x)
    {
        storeValue = x;
    }

private:
    Object storeValue;
};

int main()
{
    MemoryCell<int> m1;
    MemoryCell<string> m2{"Hello"};

    m1.write(37);
    m2.write(m2.read() + " world");
    cout << m1.read() << endl
         << m2.read() << endl;
    return 0;
}
//fig 1.23 P34
//Comparable可以是一个类类型,如Square
#include 
#include 
#include 
using namespace std;
class Square
{
public:
    explicit Square(double s = 0.0) : side{s} {}
    double getside() const
    {
        return side;
    }
    double getArea() const
    {
        return side * side;
    }
    double getPerimeter() const
    {
        return 4 * side;
    }
    void print(ostream & out=cout)const
    {
        out<<"(square "<<getside()<<")"<<endl;
    }
    bool operator<(const Square & rhs)const
    {
        return (side < rhs.getside());
    }

private:
    double side;
};

//Define an output operator for Square
ostream & operator<<(ostream &out,const Square &rhs)
{
    rhs.print(out);
    return out;
}

//figure 1.19
//return the maximum item in array a
//Assums a.size()>0
//Comparable objects must provide operator< and operator=
template<typename Comparable>
const Comparable & findMax(const vector<Comparable> &a)
{
    int maxIndex=0;
    for(int i=1;i<a.size();++i)
    {
        if(a[maxIndex]<a[i])
            maxIndex=i;
    }
    return a[maxIndex];
}
int main()
{
    vector<Square> v={Square{3.0},Square{2.0},Square{2.5}};
    cout<<"Largest square: "<<findMax(v)<<endl;
    return 0;
}

1.6.3 函数对象

//fig1.24 P35
//使用函数对象作为findMax的第二个参数的最简单思路
#include 
#include 
#include 
#include 
using namespace std;

// Generic findMax, with a function object, Version #1.
// Precondition: a.size( ) > 0.
template <typename Object, typename Comparator>
const Object &findMax(const vector<Object> &arr, Comparator cmp)
{
    int maxIndex = 0;

    for (int i = 1; i < arr.size(); ++i)
    {
        if (cmp.isLessThan(arr[maxIndex], arr[i])) //cmp内必须有isLessThan()方法
            maxIndex = i;
    }
    return arr[maxIndex];
}

//isLessThan()
class CaseInsensitiveCompare
{
public:
    bool isLessThan(const string &lhs, const string &rhs) const
    {
        return (strcasecmp(lhs.c_str(), rhs.c_str()));
    }
};

int main()
{
    vector<string> arr = {"ZEBRA", "alligator", "crocodile"};
    cout << findMax(arr, CaseInsensitiveCompare{}) << endl;
    return 0;
}
//figure1.25 P36
//泛型函数findMax(),用到了一个函数对象,C++风格
#include 
#include 
#include 
#include 
using namespace std;

// Generic findMax, with a function object, Version #1.
// Precondition: a.size( ) > 0.
template <typename Object, typename Comparator>
const Object &findMax(const vector<Object> &arr, Comparator isLessThan)
{
    int maxIndex = 0;

    for (int i = 1; i < arr.size(); ++i)
    {
        if (isLessThan(arr[maxIndex], arr[i])) //cmp内必须有isLessThan()方法
            maxIndex = i;
    }
    return arr[maxIndex];
}

//Generic findMax,using default ordering
#include 
template <typename Object>
const Object &findMax(const vector<Object> &arr)
{
    return findMax(arr, less<Object>{});//???????????????不理解
}

//函数对象,比较大小
//operator()
class CaseInsensitiveCompare
{
public:
    bool operator()(const string &lhs, const string &rhs) const
    {
        return (strcasecmp(lhs.c_str(), rhs.c_str()));
    }
};

int main()
{
    vector<string> arr = {"ZEBRA", "alligator", "crocodile"};
    cout << findMax(arr, CaseInsensitiveCompare{}) << endl;
    return 0;
}

你可能感兴趣的:(《数据结构与算法分析,C++,4ed》)