C/C++ L8

2018/08/03 C/C++

C/C++程序语言设计 Level8。

C++

C++融合了3种不同的编程方式:C语言代表的过程性语言、C++在C语言基础上添加的类代表的面向对象语言、模板支持的泛型编程。因此从C语言过渡到C++并不是简单学习更多关键字和结构,而是像从头开始学C语言一样头大。

面向对象编程

虽然结构化编程的理念提高了程序的清晰度、可靠性,并使之便于维护,但是它在编写大型程序时,仍然面临挑战。为应付这种挑战,OOP提供了一种新方法。与强调算法的过程性不同的是,OOP强调的是数据。

OOP程序设计方法首先设计类,它们准确地表示了程序要处理的东西。例如绘图程序可能定义表示矩形、直线、圆、画刷、画笔的类。类定义描述了对每个类可执行的操作,如移动圆或旋转直线。然后你便可以设计一个使用这些类的对象程序。从低级组织到高级组织的处理过程叫做自下向上的编程。

C++的起源

与C语言一样,C++也是在贝尔实验室中诞生的,Bjarne Stroustrup于20世纪80年代在这里开发出了这种语言。用他自己的话来说,“C++主要是为了我的朋友和我不必再使用汇编语言、C语言或者其他高级语言来编程而设计的。它的主要功能是可以更方便地编写出好程序,让每个程序员更加快乐。”

第一个C++程序

#include <iostream>
using namespace std;

int main(){
    cout << "Hello, world!" << endl;
    return 0;
}

编译命令为:

g++ hello.cpp -o hello
./hello

很多时候,我们会误解C++使用cout函数代替C的printf函数来输出。事实上,printf是函数,而cout本质上是一个对象,而不是函数。

输入和输出

输入使用cin关键字。看起来棒极了,因为cin支持多个输入拼接写在同一行,并且在部分情况下不需要考虑它的数据类型。

#include <iostream>
using namespace std;

int main(){
    int a;
    cin >> a;
    int b, c, d;
    cin >> b >> c >> d;

    return 0;
}

输出时使用cout关键字,同样的,cout不需要考虑数据类型,并且可以拼接写在一行。

#include <iostream>
using namespace std;

int main(){
    int a = 5;
    cout << a << endl;
    cout << "Hello, world!\n";
    cout << endl;
    return 0;
}

思考:

  1. cout使用<<运算符,这个符号在C语言中表示按位左移运算符,例如x<<3表示的是x的二进制表示中素有的位向左边移动3位。显然,这与输出的关系不大。因为C++的ostream类重新定义了<<符号,将其重载为输出。这种情况下,这个符号叫做插入运算符,而不是按位左移运算符。

  2. C++用指向字符串储存位置的指针来表示字符串,指针的形式可以是char数组名、显示的char指针或者是引号括起的字符串。例如:

     #include <iostream>
     using namespace std;
    
     int main(){
         char name[20] = "Dudly Diddly";
         char *pn = "Violet D'Amore";
         cout << "Hello" << endl;
         cout << name << endl;
         cout << pn << endl;
         return 0;
     }
    
  3. cin在输入结束或者是输入不符合程序期望的时候会返回0,所以我们可以这样改写大量输入:

     #include <iostream>
     using namespace std;
    
     int main(){
         cout << "Enter Number: ";
         int sum = 0;
         int input;
         while(cin >> input){
             sum += input;
         }
         cout << "Last value entered = " << input << endl;
         cout << "Sum = " << sum << endl;
         return 0;
     }
    

引用变量

C++新增加了一种复合类型,叫做引用变量。引用是已定义的变量的别名。例如使用twain作为element变量的引用,则可以交替使用twain和element来表示该变量。

创建引用变量

int rats;
int& rodents = rats;

这里,&符号不是地址运算符,而是类型标识符的一部分。int&表示指向int的引用。例如:

#include <iostream>
using namespace std;

int main(){
    int rats = 101;
    int& rodents = rats;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    rodents++;
    cout << "rats = " << rats;
    cout << ", rodents = " << rodents << endl;
    cout << "rats address = " << &rats;
    cout << ", rodents address = " << &rodents << endl;
    return 0;
}

引起注意

  1. 引用在声明的时候必须要初始化。

     int rat;
     int& rodent;
     rodent = rat;
     // 这是错误的。
    
  2. 引用一旦声明之后,不能再修改。

     int& rodents = rats;
     int* const rodents = &rats;
     // 这两行代码是等效的。
    

将引用变量作为函数参数

引用经常被用于作为函数参数,使得函数中的变量名成为调用程序中的变量的别名。这种传递参数的方式叫做引用传递,按照引用传递允许被调用的函数能够访问调用函数中的变量。这个特性是C++对于C的超越,C语言只能够按值传递。按值传递本质上是被调用函数使用调用函数的参数的值的拷贝。C语言可以避开按值传递的限制,那就是使用指针传递。但本质上,指针传递也是按值传递的一种。

#include <iostream>
using namespace std;

void swapr(int& a, int& b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}

void swapp(int* a, int* b){
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
}

void swapv(int a, int b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}

int main(){
    int wallet1 = 300;
    int wallet2 = 350;
    cout << "wallet1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    cout << "Using references to swap contents: " << endl;
    swapr(wallet1, wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    cout << "Using pointers to swap contents: " << endl;
    swapp(&wallet1, &wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;

    cout << "Trying to use passing by value: " << endl;
    swapv(wallet1, wallet2);
    cout << "wallet1 = $" << wallet1;
    cout << ", wallet2 = $" << wallet2 << endl;
    return 0;
}

仔细观察,其实按引用传递和按指针传递两种方法完成了同样的任务,只是在声明函数以及代码的编写上有少许差异。

不希望引用变量改变值

有的时候,函数中我们不希望引用变量导致原来变量的变化。我们使用const关键字来阻止被修改。

struct free_throws{
    int made;
    int attempts;
    float percent;
}

void set_pc(free_throws& ft);
void display(const free_throws& ft);

函数返回引用变量

例如下面的函数:

free_throws& accumulate(free_throws& target, const free_throws& source){
    target.attempts += source.attempts;
    target.made += source.made;
    set_pc(target);
    return target;
}

传统的返回机制与按值传递函数参数类似:计算关键字return后面的表达式,然后将结果返回调用函数。从概念上讲,这个值被复制到一个临时位置,而调用程序将使用这个值。例如:

double m = sqrt(16.0);
cout << sqrt(25.0);
dup = accumulate(team, five);

在第一条语句中,值4.0被复制到一个临时位置,然后被复制给m。第二条语句中,值0.5被复制到一个临时位置,然后传递给cout。第三条语句,因为accumulate函数返回引用变量,所以直接将team复制给dup,效率更高。

函数返回引用变量需要注意的问题

观察下面的代码:

const free_throws& clone(free_throws& ft){
    free_throws newguy;
    newguy = ft;
    return newguy;
}

这个函数返回一个指向临时变量的引用变量,函数运行完毕之后它将不会存在(因为生命域被销毁)。这意味着返回的引用变量指向一个不存在任何内容的地方。

思考,为什么上面的函数需要添加const?

free_throws& clone(free_throws& ft){
    free_throws newguy;
    newguy = ft;
    return newguy;
}

free_throws b;
clone(a) = b;

假如没有添加const,由于这个函数的返回值是引用变量,意味着它可以作为左值,所以上面的语句clone(a) = b;尽管看起来很怪,但是它是正确的。首先执行clone函数得到一个引用变量,然后将b的值覆盖这个引用变量。

这与我们学C语言时的思维不一样,因为一般函数的返回值是右值,所以上面的写法看起来是不对。添加const之后,上述语句将不能运行。因为clone函数返回一个引用常量,不再是左值。

Search

    Table of Contents