C++程序设计语言导论

在第一章致读者的末尾,作者写了这样一段话:“The question ‘‘How does one write good programs in C++?’’ is very similar to the question ‘‘How does one write good English prose?’’ There are two answers: ‘‘Know what you want to say’’ and ‘‘Practice. Imitate good writing.’’ Both appear to be as appropriate for C++ as they are for English – and as hard to follow.”

对于这一点实在是深有体会的,计算机语言与人类自然语言有其相似之处,我们观察儿童如何学习母语,就可以知道模仿->练习循环往复执行,从而不断提高语言能力是一个多么现实的过程,这个过程真真切切地发生在我们每个人的身上。然而计算机语言的学习更像外语的学习,如何高效地学习外语则需要我们去参考母语的学习,把母语学习的经验向外语学习进行迁移,这样的过程看上去复杂,实际上是最高效的。

在1.8节,作者给出了忠告,忠告的第一条中写道:

[1] When you program, you create a concrete representation of the ideas in your solution to some
problem. Let the structure of the program reflect those ideas as directly as possible:
[a] If you can think of ‘‘it’’ as a separate idea, make it a class.
[b] If you can think of ‘‘it’’ as a separate entity, make it an object of some class.
[c] If two classes have a common interface, make that interface an abstract class.
[d] If the implementations of two classes have something significant in common, make that
commonality a base class.
[e] If a class is a container of objects, make it a template.
[f] If a function implements an algorithm for a container, make it a template function implementing
the algorithm for a family of containers.
[g] If a set of classes, templates, etc., are logically related, place them in a common namespace.

这其实是个伏笔,在书中的第二章和第三章对此进行了解释,在稍后的章节中对此还进行了详细的解释。这其实也需要自学的经验进行配合,作者在原书中同样也说了“However, a thorough understanding of every detail mentioned in this chapter is not a requirement for understanding the following chapters. Consequently, you may prefer to skim through this chapter, observing the major concepts, and return later as the need for understanding of more details arises.” 对于自学者而言,书是需要读多遍的,至少读两遍,第一遍可以称为预习,第二遍称为温习或复习,孔子说温故而知新,可以为师也。可以说温故总是可以得到新知的,除非你第一遍根本就一点没懂,那就需要更多遍的阅读或学习了。

关于作者的忠告,还想再补充两句。不知道作者是否知道Alan Perlis说的这句话:“每个人都可以被教授如何雕塑;而对米开朗基罗来说,能教给他的倒是怎样能够不去雕塑。杰出的程序员也一样”。作者在忠告中的表述完全达到了Alan Perlis以上这句话的效果,换言之,这本书是为杰出程序员预备的。

基于作者指出的计算机语言学习来源于模仿和实践,对C++语言的学习将采用对书中案例进行模仿和实践的学习方法,给出程序运行的结果并加以分析。

//过程式程序设计
//page 21
// No guarantees offered.

#include <iostream>
double sqrt(double arg)
{
    double err = 0.0000001;
    double low = 0.0;
    double high = arg;
    double mid = (low + high) / 2;
    while ((high-low)>err)
    {
        if (mid*mid >arg)
        {
            high = mid;
        }
        else
        {
            low = mid;
        }
        mid = (high + low)/2;
    }
    return mid;
}

/*void f()
{
    double root2= sqrt(2);
}*/

int main()
{
    double root2= sqrt(2);
    std::cout<<root2<<"\n";
}

//过程式程序设计
//page 23
//No guarantees offered.
#include <iostream>

using namespace std;

int main()
{
    int tries = 1;
    while (tries < 4 )
    {
    cout<<"Do you want to proceed (y or n )?\n"; // write question
    char answer = 0;
    cin >> answer; // read answer
    switch (answer)
    {
    case 'y':
    return true;
    case 'n':
    return false;
    default :
    cout<<"Sorry, I don't understand that.\n";
    tries = tries + 1;
    }
    }
    cout<<"I'll take that for a no.\n";
    return false;

    //cout << "Hello world!" << endl;
    //return 0;
}
Do you want to proceed (y or n )?
1
Sorry, I don't understand that.
Do you want to proceed (y or n )?
1
Sorry, I don't understand that.
Do you want to proceed (y or n )?
1
Sorry, I don't understand that.
I'll take that for a no.

Process returned 0 (0x0)   execution time : 10.571 s
Press any key to continue.
// Simple inch<->cm conversion program illustrating basic I/O and computation.

// pg 50, sec 3.6, Input

// No guarantees offered. Constructive comments to bs@research.att.com



#include<iostream>

using namespace std;


int main()
{
	const float factor = 2.54;	// 1 inch equals 2.54 cm
	float x, in, cm;
	char ch = 0;

	cout << "enter length: ";

	cin >> x;		// read a floating-point number
	cin >> ch;		// read a suffix

	switch (ch) {
	case 'i':		// inch
		in = x;
		cm = x*factor;
		break;
	case 'c':		// cm
		in = x/factor;
		cm = x;
		break;
	default:
		in = cm = 0;
		break;
	}

	cout << in << " in = " << cm << " cm\n";

	return 0;	// redundant in ISO C++
}
enter length: 1
l
0 in = 0 cm

Process returned 0 (0x0)   execution time : 10.191 s
Press any key to continue.
// program illustrating basic I/O.

// pg 45, sec 3.6, Input

// No guarantees offered.



#include<iostream>

using namespace std;


int main()
{
    string str;
    cout<<"Please enter your name\n";
    cin >>str;
    cout<<"Hello,"<<str<<"!\n";
}
Please enter your name
H XP
Hello,H!

Process returned 0 (0x0)   execution time : 26.079 s
Press any key to continue.

// program illustrating basic I/O.

// pg 45, sec 3.6, Input

// No guarantees offered.



#include<iostream>

using namespace std;


int main()
{
    string str;
    cout<<"Please enter your name\n";
//    cin >>str;
    getline(cin,str);
    cout<<"Hello,"<<str<<"!\n";
}
Please enter your name
Eric Bloodaxe
Hello,Eric Bloodaxe!

Process returned 0 (0x0)   execution time : 13.009 s
Press any key to continue.

// Count occurrences of character in string using find()
// to illustrate simple use of iterators and algorithms.

// pp 57-58, sec 3.8.1, Use of Iterators

// No guarantees offered. Constructive comments to bs@research.att.com


#include<iostream>
#include<algorithm>
#include<string>

using namespace std;


int count(const string& s, char c)	// count occurrences of c in s
{
	int n = 0;
	string::const_iterator i = find(s.begin(),s.end(),c);
	while (i != s.end()) {
		++n;
		i = find(i+1,s.end(),c);
	}
	return n;
}


void f()
{
	string m = "Mary had a little lamb";
	int a_count = count(m,'a');

	cout << "\"" << m << "\" contains " << a_count << " 'a's\n";
}

int main()
{
	f();

	return 0;	// redundant in ISO C++
}

"Mary had a little lamb" contains 4 'a's

Process returned 0 (0x0)   execution time : 0.010 s
Press any key to continue.

// Count occurrences of elements in various containers using find()
// to illustrate generic algoriths and iterators.

// pp 58-59, sec 3.8.1, Use of Iterators

// No guarantees offered. Constructive comments to bs@research.att.com


#include<iostream>
#include<algorithm>
#include<string>
#include<complex>
#include<vector>
#include<list>

using namespace std;

typedef complex<double> dcomplex;	// In the book, I cheated slightly, sorry
// the standard library complex is a template


template<class C, class T> int count(const C& v, T val)
{
	typename C::const_iterator i = find(v.begin(),v.end(),val);	// "typename;" see C.13.5
	int n = 0;
	while (i != v.end()) {
		++n;
		++i;	// skip past the element we just found
		i = find(i,v.end(),val);
	}
	return n;
}

void f(list<dcomplex>& lc, vector<string>& vc, string s)
{
	int i1 = count(lc,dcomplex(1,3));
	int i2 = count(vc,"Chrysippus");
	int i3 = count(s,'x');

	cout << "number of complex(1,3)s " << i1 << "\n";
	cout << "number of \"Chrysippus\"s " << i2 << "\n";
	cout << "number of 'x's in \"" << s << "\" : " << i3 << "\n";
}

int main()
{
	vector<string> philo;
	philo.push_back("Aristotle");
	philo.push_back("Plato");
	philo.push_back("Chrysippus");
	philo.push_back("Zeno");

	list<dcomplex> spline;
	spline.push_back(dcomplex(1,3));
	spline.push_back(dcomplex(1,7));
	spline.push_back(dcomplex(1,3));
	spline.push_back(dcomplex(7,3));

	string boat = "Exxon Valdez";

	f(spline,philo,boat);

	return 0;	// redundant in ISO C++
}
number of complex(1,3)s 2
number of "Chrysippus"s 1
number of 'x's in "Exxon Valdez" : 2

Process returned 0 (0x0)   execution time : 0.009 s
Press any key to continue.

根据上面这个完整的程序的启发,就可以对2.5节模块化和数据抽象栈Stack进行实现了。

先给出头文件stack.h

//模块化程序设计(类和数据抽象)
//No guarantees offered.


#ifndef STACK_CLASS_H_INCLUDED
#define STACK_CLASS_H_INCLUDED



#endif // STACK_CLASS_H_INCLUDED
class Stack
{
    char* v;
    int top;
    int max_size;
public:
    //class Underflow{};
    //class  Overflow{};
    //class  Bad_size{};

    Stack(int s);
    ~Stack();

    void push(char c);
    char pop();

};

接下来给出Stack的具体实现Stack.c:

//模块化程序设计(类和数据抽象)
//No guarantees offered.


#include "stack class.h"
#include<iostream>
using namespace std;
Stack::Stack(int s)
{
    top=0;
    //if(s<0||10000<s) throw Bad_size();
    if(s<0||10000<s) cout<<"Bad size."<<endl;
    max_size=s;
    v=new char[s];
}

Stack::~Stack()
{
    delete[] v;
}

void Stack::push(char c)
{
    //if(top==max_size) throw Overflow();
    if(top==max_size) cout<<"Overflow."<<endl;
    v[top]=c;
    top=top+1;
}

char Stack::pop()
{
    //if (top==0) throw Underflow();
    if (top==0) cout<<"Underflow."<<endl;
    top=top-1;
    return v[top];
}

 最后给出一个简单的应用user.c

//模块化程序设计(类和数据抽象)
//No guarantees offered.

#include "stack class.h"
#include<iostream>

using namespace std;

int main()
{
    Stack s_var1(10);
    s_var1.push('a');
    s_var1.push('A');
    cout<<s_var1.pop()<<"\n";
}

运行结果如下所示:

A

Process returned 0 (0x0)   execution time : 0.008 s
Press any key to continue.

总结以上模块化程序设计的思路:用户代码放在user.c中,在stack.c和user.c中的代码共享通过stack.h提供界面,除此之外这两个文件就是互不相关的,可以分别进行编译。 

抽象类型的实现:

首先给出头文件:

#ifndef STACK_H_INCLUDED
#define STACK_H_INCLUDED



#endif // STACK_H_INCLUDED
class Stack
{

public:
    class Underflow{};
    class  Overflow{};

    virtual void push(char c)=0;
    virtual char pop()=0;

};


class Array_stack:public Stack
{
    char* p;
    int top;
    int max_size;
public:
    Array_stack(int s);
    ~Array_stack();
    void push(char c);
    char pop();
};

给出stack具体实现:

#include"Stack.h"
#include<iostream>
using namespace std;



Array_stack::Array_stack(int s)
{
    top=0;
    //if(s<0||10000<s) throw Bad_size();
    if(s<0||10000<s) cout<<"Bad size."<<endl;
    max_size=s;
    p=new char[s];
}

Array_stack::~Array_stack()
{
    delete[] p;
    cout<<"delete complete.\n";
}

void Array_stack::push(char c)
{
    //if(top==max_size) throw Overflow();
    if(top==max_size) cout<<"Overflow."<<endl;
    p[top]=c;
    top=top+1;
}

char Array_stack::pop()
{
    //if (top==0) throw Underflow();
    if (top==0) cout<<"Underflow."<<endl;
    top=top-1;
    return p[top];
}

最后给出一个应用和显示结果:

#include <iostream>
#include "Stack.h"
using namespace std;

int main()
{
    Array_stack Array1(10);
    Array1.push('a');
    Array1.push('A');
    cout<<Array1.pop()<<"\n";
    cout << "Hello world!" << endl;
    return 0;
}
A
Hello world!
delete complete.

Process returned 0 (0x0)   execution time : 0.016 s
Press any key to continue.

这里需要指出的是程序是在运行结束时自动调用了析构函数 ~Array_stack()。

虚函数的机理,先给出一段代码:

首先是头文件

#ifndef STACK_H_INCLUDED
#define STACK_H_INCLUDED



#endif // STACK_H_INCLUDED
#include<list>

using namespace std;



class Stack
{

public:
    class Underflow{};
    class  Overflow{};

    virtual void push(char c)=0;
    virtual char pop()=0;

};


class Array_stack:public Stack
{
    char* p;
    int top;
    int max_size;
public:
    Array_stack(int s);
    ~Array_stack();
    void push(char c);
    char pop();
};

class List_stack:public Stack
{
    list<char> lc;
public:
    List_stack() {}
    void push(char c);
    char pop();
};

然后是被封装隐藏的stack

#include"stack.h"
#include<iostream>
#include<list>
using namespace std;

Array_stack::Array_stack(int s)
{
    top=0;
    //if(s<0||10000<s) throw Bad_size();
    if(s<0||10000<s) cout<<"Bad size."<<endl;
    max_size=s;
    p=new char[s];
}

Array_stack::~Array_stack()
{
    delete[] p;
    cout<<"delete complete.\n";
}

void Array_stack::push(char c)
{
    //if(top==max_size) throw Overflow();
    if(top==max_size) cout<<"Overflow."<<endl;
    p[top]=c;
    top=top+1;
}

char Array_stack::pop()
{
    //if (top==0) throw Underflow();
    if (top==0) cout<<"Underflow."<<endl;
    top=top-1;
    return p[top];
}

void List_stack::push(char c)
{
    lc.push_front(c);
}

char List_stack::pop()
{
    char x=lc.front();
    lc.pop_front();
    return x;
}

最后给出案例:

#include <iostream>
#include"stack.h"
using namespace std;

int main()
{
    Array_stack as(10);
    List_stack  ls;
    as.push('a');
    as.push('A');
    ls.push('b');
    ls.push('B');
    cout<<as.pop()<<"\n";
    cout<<ls.pop()<<"\n";
    cout << "Hello world!" << endl;
    return 0;
}

显示的执行结果如下:

A
B
Hello world!
delete complete.

Process returned 0 (0x0)   execution time : 0.010 s
Press any key to continue.

 引用书中原文如下:The functions in the vtbl allow the object to be used correctly even when the size of the object and the layout of its data are unknown to the caller. All the caller needs to know is the location of the vtbl in a Stack and the index used for each virtual function. This virtual call mechanism can be made essentially as efficient as the ‘‘normal function call’’ mechanism. Its space overhead is one pointer in each object of a class with virtual functions plus one vtbl for each such class.

关于堆栈,作者就死磕到底了,堆栈是一个对于计算机硬件具体而对于实际应用抽象和一般的概念。那么实际应用中有使用字符的堆栈,也有使用整数的堆栈,如何隐藏实现细节,实现对实际应用透明而且独立的表达了。作者给出了这样的程序设计范式:

 正如公文写作有模板,论文写作有模板,规范化给语言的交流提供了方便阅读的便利条件。

同样,在程序设计中也可以套用模板实现堆栈,以下给出具体的程序实现。

#ifndef STACK_H_INCLUDED
#define STACK_H_INCLUDED

#endif // STACK_H_INCLUDED

#include<iostream>
using namespace std;

template<class T>class Stack
{
    T* v;
    int top;
    int max_size;
public:
    class Underflow{};
    class Overflow{};

    Stack(int s);
    ~Stack();

    void push(T);
    T pop();
};

template<class T>Stack<T>::Stack(int s)
{
    top=0;
    if(s<0||10000<s) cout<<"Bad size."<<endl;
    max_size=s;
    v=new T[s];
}

template<class T>Stack<T>::~Stack()
{
    delete[] v;
    cout<<"delete complete.\n";
}

template<class T>void Stack<T>::push(T c)
{
    if (top==max_size)
    {
        throw Overflow();
        cout<<"Overflow."<<endl;
    }
    v[top]=c;
    top=top+1;
}

template<class T>T Stack<T>::pop()
{
    if (top==0)
    {
        throw Underflow();
        cout<<"Underflow."<<endl;
    }
    top=top-1;
    return v[top];
}

给出一个具体应用案例如下:

#include<iostream>
#include<complex>
#include"stack.h"
using namespace std;

typedef complex<int> dcomplex;
int main()
{
    Stack<char> sc(20);
    Stack<dcomplex> scplx(30);


    sc.push('c');
    scplx.push(dcomplex(1,2));


    cout<<sc.pop()<<"\n";
    cout<<scplx.pop()<<"\n";


}

运行结果如下所示:

c
(1,2)
delete complete.
delete complete.

Process returned 0 (0x0)   execution time : 0.007 s
Press any key to continue.

这里,作者给出了一个定义,一个能保存某种类型的一集元素的类,一般被称为一个容器类,或简单地称为容器。(A class holding a collection of elements of some type is commonly called a container class, or simply a container.

在C++程序设计语言第四版(最新版)中,增加了并发这一章节,考虑到Bjarne Stroustrup已经是70多岁的高龄,这一版有可能成为绝版。众所周知,现代的cpu处理器无论是ARM架构还是x86架构,早已经迈入了多核心时代。并发,也就是多个任务同时执行成为编程提高吞吐率(用多个处理器共同完成单个计算)和提高响应速度(允许程序的一部分在等待响应时,另一部分继续执行)。然而多任务同时执行有别于单任务,需要在程序中做一些特殊的处理,否则往往会出现意想不到的结果(非预料的运行结果)。以下给出具体的例子。

#include <iostream>
#include <thread>
using namespace std;

void f()
{
    cout<<"Hello ";
}

struct F
{
    void operator()(){cout<<"Parallel World!\n";}
};


int main()
{
    thread t1 {f};
    thread t2 {F()};

    t1.join();
    t2.join();

    return 0;

}

输出结果如下所示:

Hello Parallel World!

Process returned 0 (0x0)   execution time : 0.006 s
Press any key to continue.

这样的运行结果是我们想要的,然而Bjarne Stroustrup却指出在这个程序中,f和F()都使用了对象cout,但没有采取任何形式的同步措施。因此输出结果将是不可预测的,而且程序每一次执行都可能得到不同结果,毕竟两个任务中的操作的执行顺序根本无法确定。程序可能会产生下面这样的“奇怪的”输出:PaHerallllel o World!

我又重新运行了几次程序,分别得到的输出结果如下:

Parallel World!
Hello
Process returned 0 (0x0)   execution time : 0.007 s
Press any key to continue.

该程序在一个单核CPU上运行,得到如下错误:

terminate called after throwing an instance of 'std::system_error'
  what():  Enable multithreading to use std::thread: Operation not permitted
已放弃 (核心已转储)

Bjarne Stroustrup对这个问题的解决方案是:在定义一个并发程序的任务时,我们的目标是保持任务的完全隔离,唯一的例外是任务间的通信部分;这种通信应该以一种简单明显的方式进行。考虑一个并发任务的最简单的方式是把它看作一个可以与调用者并发执行的函数。为此,我们只需要传递实参、获取结果并保证两者不会同时使用共享数据(不存在数据竞争)。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值