C/C++基本语法

2019/10/08 C/C++

这里简单记录了C/C++的基本语法,难度系数一颗星。

计算机程序

The programming language you will be learning is C, which was developed in the early 1970s by Dennis M. Ritchie at the Bell Laboratories. C is an example of a high-level language; other high-level languages you might have heard of are Pascal, C++ and Java.

There are two ways to translate a program: interpreting or compiling. An interpreter is a program that reads a high-level program and does what it says. In effect, it translates the program line-by-line, alternately reading lines and carrying out commands.

A compiler is a program that reads a high-level program and translates it all at once, before executing any of the commands. Often you compile the program as a separate step, and then execute the compiled code later. In this case, the high-level program is called the source code, and the translated program is called the object code or the executable.

C语言

C语言是计算机高级语言。它的祖先是BCPL语言。1967年英国剑桥大学的Martin Richards推出了Basic Combined Programming Language语言,1970年美国AT&T贝尔实验室的Ken Thompson以BCPL语言为基础,设计出很简单并且接近硬件的B语言。1972-1973年间,美国贝尔实验室的D.M.Richie在B语言的基础上设计出了C语言。

windows下C/C++编程环境

  1. 访问http://www.mingw.org/,下载mingw工具。
  2. 下载sublime text编辑器、VScode编辑器。
  3. 启动mingw安装器,选择mingw-develop-toolkit、mingw32-base、mingw32-gcc-g++、msys-base,点击Installation – Apply Changes。
  4. 配置环境变量:
    • C_INCLUDEDE_PATH C:\MinGW\include
    • LIBRARY_PATH C:\MinGW\lib
    • Path C:\MinGW\bin
  5. 编写代码,保存为test.c。
  6. 访问https://git-scm.com/,安装git-bash工具。
  7. 启动git-bash工具,输入命令:
    gcc test.c -o c
    ./c
    
  8. 查看gcc返回的结果。

或者,最简单的方式:

  1. 安装啊哈C编程工具或者是C-Free等IDE。

什么是一个程序?

A program is a sequence of instructions that specifies how to perform a computation. The computation might be something mathematical, like solving a system of equations or finding the roots of a polynomial, but it can also be a symbolic computation, like searching and replacing text in a document or (strangely enough) compiling a program.

The instructions, which we will call statements, look different in different programming languages, but there are a few basic operations most languages can perform:

  1. input: Get data from the keyboard, or a file, or some other device.
  2. output: Display data on the screen or send data to a file or other device.
  3. math: Perform basic mathematical operations like addition and multiplication.
  4. testing: Check for certain conditions and execute the appropriate sequence of statements.
  5. repetition: Perform some action repeatedly, usually with some variation.

调试错误

程序是一个复杂的操作过程,人们在写代码的时候可能会犯错。我们称计算机程序出现的错误为bugs,而调试错误的过程称为debugging。下面是常见的错误:

  1. Compile-time errors
    • 编译过程错误,原因是违反语法规则。
  2. Run-time errors
    • 运行时错误,在程序运行时出现的错误。
  3. Logic errors and semantics
    • 逻辑设计错误,程序能够正常运行,但是不能解决问题。

数据的表现形式以及声明语句

常量

程序运行过程中,值不能改变的量称为常量。声明常量语句使用const关键字。常量包含:

  1. 整型常量
  2. 实型常量
  3. 字符常量(普通字符、转义字符)
  4. 字符串常量
  5. 符号常量(使用define指令)

变量

程序运行过程中,变量代表的是一个有名字的、具有特定属性的一个存储单元,它用来存放数据,也就是存放变量的值。在程序运行期间,变量的值是可以改变的。变量需要先定义,然后才能使用。

标识符

在计算机语言中,用来对变量、符号常量名、函数、数组、类型等命名的有效字符序列统称为标识符。换句话说,标识符就是对象的名字,例如前面提到的变量名就是标识符的一种。标识符的命名规则是只能由数字、字母以及下划线组成,其中不能以数字开头。标识符是对大小写敏感的。

数据类型

C语言规定了下列的基本数据类型(C99标准),不同的数据类型决定了数据分配存储单元的大小。

  1. 基本类型
    • int
    • short
    • long long
    • char
    • bool
    • float
    • double
  2. 枚举类型
  3. void
  4. 派生类型
    • 指针
    • 数组
    • 结构体
    • 共用体
    • 函数

该小节中介绍基本类型,其中可以使用sizeof函数来识别不同类型的数据长度。

int

编译系统给int类型分配2个字节或者是4个字节,不同的编译器分配的空间可能不同。int在储存结构的最前面的1bit数据是表示正负号。

short

跟int类似,仅是储存长度变为2字节。一般比较少用。

long long

跟int类似,储存长度变为8字节,用于储存十分大的整数。

字符

C语言使用ASCII字符集,ASCII基本集包含了127个字符,包括

  • 字母:大写英文字母A-Z、小写英文字母a-z。
  • 数字:0-9。
  • 符号:共29个符号,例如%&*等。
  • 空格符:空格、tab、换行等等。
  • 不可显示:空(\0)、退格、回车等。

char类型可以储存字符,本质上char是一个1字节的整数变量,对应的是0-127整数,每个整数映射到一个字符,参见ASCII表。

float

单精度浮点数。储存的时候,系统将实数分为小数部分以及指数部分,储存结构是数符、小数部分、指数

double

类似的,双精度浮点数,储存长度8字节。

运算符

几乎每一个程序都需要进行运算,对数据进行加工处理,否则程序就没有意义了。C语言规定的基本运算符号包含:+ - * / %。

自增、自减运算符号

++i; (先加,后用) i++; (先用,后加)

当自增自减符号单独使用的时候,没有任何区别。

运算自动转换

如果程序中不同的数据进行运算,则会自动进行类型转换,使得两者具有同一种类型,然后进行计算。

  1. 单目运算符的运算中,两个数有一个float,另一个是double,则结果为double。float类型转换为double类型。
  2. 如果int与float、double类型进行运算,则int转换为double类型。
  3. char类型与int类型进行运算,则将char的ASCII序号与int相加后转换为ASCII字符。

赋值声明语句

赋值符号=,是赋值运算符,它的作用是将一个数据赋予一个变量或者是常量。一般在声明变量、变量更变值的时候使用到赋值语句,并且在初始化变量的时候赋0。

赋值运算符可以使用复合形式,例如a = a + 3可以这么表达:

a += 3

样例1

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

int main(){
    double a, b, c, disc, x1, x2, p, q;
    cin >> a >> b >> c
    disc = b*b - 4*a*c;
    p = -b/(2.0*a);
    q = sqrt(disc)/(2.0*a);
    x1 = p+q;
    x2 = p-q;
    cout << x1 << ' ' << x2 << endl;
    return 0;
}

样例2

// 思考如何改进支持更多变换
#include <iostream>
using namespace std;

int main(){
    char c1, c2;
    cin >> c1;
    c2 = c1 + 32;
    cout << c2 << endl;
    return 0;
}

选择结构程序设计

顺序结构程序设计中,各语句是按照自上而下的顺序执行的,执行完上一个语句就自动执行下一个语句,是无条件的、不需要判断的。这是最简单的程序结构。实际上,在很多情况下,需要根据某个条件是否满足来决定是否执行指定的操作任务,或者从给定的两种或多种操作选择其一,这是选择结构要解决的问题。

用if语句处理选择结构

实例1

#include <iostream>
using namespace std;

int main(){
    float a, b, c, t;
    cin >> a >> b >> c;
    if(a>b){
        t=a;
        a=b;
        b=t;
    } 
    if(a>c){
        t=a;
        a=c;
        c=t;
    }
    if(b>c){
        t=b;
        b=c;
        c=t;
    }
    cout << a << " " << b << " " << c << endl;
    return 0;
}

if语句的一般形式是:

if(表达式)
    语句1
else
    语句2

if(表达式)
    语句1
else if(表达式)
    语句2
else
    语句3

逻辑运算符

有时要求判断的条件不是一个简单的条件,而是由几个给定简单条件组成的复合条件。例如语句“如果周六不下雨,我就去公园玩”,这里包含两个简单的复合条件,第一是否周六,第二是否下雨,只有两个条件都满足的情况下才能去公园玩。

常见的两个逻辑表达式分别是AND以及ORAND表示“和”,也就是两个条件同时为真时整个式子才为真,其余情况都为假。OR表示“或”,也就是两个条件至少有1个为真时整个式子为真,其余情况为假。

在C语言中,用&&表示AND,用||表示OR。

上面提到的&&||符号是双目运算符号,还有一个逻辑符号是单目的,叫做非,用表示。如果a为假,那么!a为真,如果a为真,!a为假。

逻辑表达式

逻辑表达式的值是一个“真”或者“假”。C语言在表达逻辑运算结果的时候,以数值1表示真,以0表示假。但是当判断某个量是真还是假的时候,将0当作是假,非0当作是真。例如:

  1. a=4,则a为真,!a为假。
  2. a=4, b=5,则a&&b为真,是1。

练练手:判断year是否闰年,满足闰年满足两个条件之一:能被4整除但是不能被100整除;能被400整除。

(year%4 == 0 && year%100!=0) || year%400==0

三目运算符

有一种if语句,当被判别的表达式的值为真或者假的时候,都执行一个赋值语句并且向同一个变量赋值,例如:

if(a>b)
    max = a;
else
    max = b;

当a>b时将a的值赋予max,当a<=b的时候将b的值赋予max。可以看到无论是什么条件,都是赋予max某个值,这种类型的if语句可以用三目运算符来改写:

max = (a>b) ? a : b;

示例:

#include <iostream>
using namespace std;

int main(){
    char ch;
    cin >> ch;
    ch = (ch >= 'A' && ch <= 'Z') ? (ch + 32) : ch;
    cout << ch << endl;
    return 0;
}

选择结构的嵌套

在if语句中又包含一个或者多个if语句称为if语句的嵌套。

示例:

#include <iostream>
using namespace std;

int main(){
    int x, y;
    cin >> x;
    if(x<0){
        y = -1;
    }
    else{
        if(x==0){
            y = 0;
        }
        else{
            y = 1;
        }
    }
    cout << "x=" << x << ",y=" << y << endl;
    return 0;
}

switch语句

if语句只有两个分支可以使用,实际问题中会有多个分支的选择,例如,学生的成绩按照85分以上为A,70-84分为B,60-69分为C等等,人口统计分为老、中、青、少、儿童等等。尽管嵌套的if语句也能表达多分支,但是代码不够简洁。C语言使用switch来处理多分支的问题。

示例:

#include <iostream>
using namespace std;

int main(){
    char grade;
    cin >> grade;
    cout << "你的成绩区间是: ";
    switch(grade){
        case 'A': cout << "85-100" << endl; break;
        case 'B': cout << "70-84" << endl; break;
        case 'C': cout << "60-69" << endl; break;
        case 'D': cout << "<60" << endl; break;
        default: cout << "输入的数据不合理" << endl; break;
    }
    return 0;
}

注意事项:

  1. switch的条件表达,其值应该为整数类型(包括char)。
  2. case后面的表达式是常量。
  3. default不是必须的,可以省略。
  4. break语句是跳出switch,如果缺失break则会连续执行case。

实例1

改进的求解一元二次方程:

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

int main(){
    double a, b, c, disc, x1, x2, realpart, imagpart;
    cin >> a >> b >> c;
    cout << "结果是:" << endl;

    if(fabs(a) <= 1e-6){
        cout << "格式错误" << endl;
    } else {
        disc = b*b - 4*a*c;
        if(fabs(disc) <= 1e-6){
            cout << "两个相同的根:" << -b/(2*a) << endl;
        } else {
            if(disc > 1e-6){
                x1 = (-b+sqrt(disc))/(2*a);
                x2 = (-b-sqrt(disc))/(2*a);
                cout << "两个实数根:" << x1 << " and " << x2 << endl; 
            } else {
                realpart = -b/(2*a);
                imagpart = sqrt(-disc)/(2*a);
                cout << "两个复数根:" << endl;
                cout << realpart << "+" << imagpart << endl;
                cout << realpart << "-" << imagpart << endl;
            }
        }
    }
    return 0;
}

循环结构程序设计

日常生活中或在程序所处理的问题中常常遇到需要重复处理的问题,例如:

  1. 对计算机输入全班50个学生的成绩。
  2. 统计全班50个同学的平均成绩。
  3. 求100个整数的和。
  4. 检查100个学生的成绩是否合格,等等。

顺序结构能够实现这种重复性的工作,但是这种做法不可取,因为工作量大、程序冗长、重复、难以阅读与维护。C语言中使用循环结构来处理重复操作。

用while语句处理循环结构

实例

#include <iostream>
using namespace std;

int main(){
    int i = 1, sum = 0;
    while(i <= 100){
        sum = sum + i;
        i++;
    }
    cout << "sum=" << sum << endl;
    return 0;
}

用do…while语句处理循环结构

实例

#include <iostream>
using namespace std;

int main(){
    int i = 1, sum = 0;
    do{
        sum = sum + i;
        i++;
    }while(i<=100);
    cout << "sum=" << sum << endl;
    return 0;
}

用for语句处理循环结构

除了使用while、do…while语句实现循环之外,C语言还提供for语句实现循环,而且for语句更为灵活,不仅可以用于循环次数已经确定的情况,还可以用于循环次数不确定而只给出循环结束条件的情况,它完全可以代替while语句工作。

for语句的格式:

for(循环变量赋予初值;循环条件;循环变量递增){ 循环体 }

实例

#include <iostream>
using namespace std;

int main(){
    int sum = 0;
    for(int i=1;i<=100;i++){
        sum += i;
    }
    cout << "sum=" << sum << endl;
    return 0;
}

循环结构的嵌套

一个循环体内部包含另一个完整的循环体,称为循环结构的嵌套。这就是多重循环,常见的多重循环是二层、三层,四层以及以上的嵌套很少,也不建议使用。

循环结构体的提前终止

正常的循环结构终止是依赖指定的跳出条件。但有的时候在某种特定情况下,我们需要提前终止正在执行的循环操作。循环结构中用break语句以及continue语句来实现。

用break语句终止循环

问题:现在全系1000个学生中,征集慈善募捐,当总数达到10W元时就结束,并统计此时募捐的人数、以及平均每人捐款的数目。

实例

#include <iostream>
#define SUM 100000
using namespace std;

int main(){
    double amount, aver, total=0;
    for(int i=1;i<=1000;i++){
        cout << "请输入捐款的金额:" << endl;
        cin >> amount;
        total = total + amount;
        if(total >= SUM){
            aver = total/i;
            cout << "num=" << i << ",aver=" << aver << endl;
            break;
        }
    }
    return 0;
}

注意,break只能用于循环语句以及switch语句中。

用continue语句终止循环。

continue与break的区别是,continue只是终止1次循环,而break是终止整个循环。

问题:要求输出100-200之间不能被3整除的数。

#include <iostream>
using namespace std;

int main(){
    for(int n=100;n<=200;n++){
        if(n%3==0)
            continue;
        else
            cout << n << " ";
    }
    cout << endl;
    return 0;
}

实例2

#include <iostream>
using namespace std;

int main(){
    for(int i=1;i<=4;i++){
        for(int j=1;j<=5;j++,n++){
            if(n%5==0)
                cout << endl;
            cout << i*j << '\t';
        }
    }
    cout << endl;
    return 0;
}

实例3

求解圆周率的近似公式:

PI = 4*(1-1/3+1/5-1/7+1/9...)

求解PI的近似值,保留小数8位。

#include <iostream>
using namespace std;

int main(){
    int sign = 1;
    double PI = 0.0, term = 1.0;
    int n = 0;

    while(fabs(term)>=1e-6){
        PI += term;
        n += 2;
        sign *= -1;
        term = sign/n;
    }
    PI *= 4;
    cout << "PI=" << PI << endl;
    return 0;
}

实例4

求解斐波那契数列的前40个数,规定f1 = 1,f2 = 1。

#include <iostream>
using namespace std;

int main(){
    int f1 = 1;
    int f2 = 1;
    int f3 = 0;
    cout << f1 << endl << f2 << endl;
    for(int i=1;i<=38;i++){
        f3 = f1 + f2;
        cout << f3 << endl;
        f1 = f2;
        f2 = f3;
    }
    return 0;
}

数组

C语言使用数组处理批量数据。

  1. 数组是一组有序数据的集合。下标代表数据在数组中的位置。
  2. 用数组名和下标来唯一确定数据在数组中的元素。
  3. 数组中的每一个元素都属于同一个数据类型,不能把不同类型的数据放在同一个数组中。

定义与引用一维数组

定义一维数组的形式是:

类型符 数组名[常量表达式];

引用一维数组的形式是:

数组名[下标]

实例:对10个数组元素依次赋值0-9,然后逆序输出。

#include <iostream>
using namespace std;

int main(){
    int a[10];
    for(int i=0;i<=9;i++){
        a[i] = i;
    }
    for(int i=9;i>=0;i--){
        cout << a[i] << " ";
    }
    cout << endl;
    return 0;
}

一维数组的初始化

示例:

#include <iostream>
using namespace std;

int main(){
    int a[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
    int b[10] = {0, 1, 2, 3};
    int c[10] = {0};
    int d[] = {1, 2, 3, 4, 5};
}

实例:用数组来处理斐波那契数列问题。

#include <iostream>
using namespace std;

int main(){
    int f[20] = {1, 1};
    for(int i=2;i<20;i++){
        f[i] = f[i-2] + f[i-1];
    }
    for(int i=0;i<20;i++){
        cout << f[i] << " ";
    }
    cout << endl;
    return 0;
}

简单排序

排序问题属于计算机算法的范畴,这里以冒泡排序为例。

实例

#include <iostream>
using namespace std;

int main(){
    int a[10];
    for(int i=0;i<10;i++){
        cin >> a[i];
    }
    for(int i=0;i<9;i++){
        for(int j=0;j<9-i;j++){
            if(a[j]>a[j+1]){
                int t = a[j];
                a[j] = a[j+1];
                a[j+1] = t;
            }
        }
    }
    for(int i=0;i<10;i++){
        cout << a[i] << " ";
    }
    cout << endl;
    return 0;
}

二维数组

二维数组常常称为矩阵,把二维数组写成行、列的排列形式,它跟普通的表格很类似。注意,尽管二维结构有行、列的描述,但是实际上二维数组在计算机内依旧是线性的储存。

实例:给定一个3*4的矩阵,要求编程求解其中最大的元素的值。

#include <iostream>
using namespace std;

int main(){
    int a[3][4] = {
        {1,2,3,4}, 
        {9,8,7,6}, 
        {-10,10,-5,2}
    };
    int max = -10000;
    for(int i=0;i<3;i++){
        for(int j=0;j<4;j++){
            if(a[i][j]>max){
                max = a[i][j];
            }
        }
    }
    cout << "最大值是:" << max << endl;
    return 0;
}

字符数组

字符型的数据类型使用关键字char,占用1个字节。C语言中没有字符串的类型,而是使用字符型数据的数组来储存字符串。

示例:

#include <iostream>
using namespace std;

int main(){
    char a[10] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y'};
    char b[] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y'};
}

C语言使用’\0’表示字符串的结束位置,C系统在字符数组储存字符串的时候自动添加该符号,占据1个字节。下面是声明字符串的例子:

#include <iostream>
using namespace std;

int main(){
    char c[] = "I am happy";
    // 这个数组实际上是占用11个字节
}

输入、打印字符串的例子:

#include <iostream>
using namespace std;

int main(){
    char c[] = "Hello World!";
    cout << c << endl;

    char str[13];
    cin >> str;
    cout << str << endl;
    return 0;
}

实例5

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

int main(){
    for(int i=2;i<=100;i++){
        int temp = 0;
        for(int j=2;j<=sqrt(i);j++){
            if(i%j==0){
                temp = 1;
                break;
            }
        }
        if(!temp){
            cout << i << " ";
        }
    }
    cout << endl;
    return 0;
}
#include <iostream>
#include <cmath>
using namespace std;

int main(){
    char c1[1000];
    char c2[1000];
    cin >> c1 >> c2;
    int position = 0;
    for(int i=0;i<1000;i++){
        if(c1[i] == '\0'){
            position = i;
            break;
        }
    }
    for(int i=0;i<1000;i++){
        c1[position+i]=c2[i];
        if(c2[i] == '\0'){
            break;
        }
    }
    cout << c1 << endl;
    return 0;
}
#include <iostream>
using namespace std;

int main(){
    char chipText[1000];
    char plainText[1000];
    cin >> chipText;
    for(int i=0;i<1000;i++){
        if(chipText[i] == '\0'){
            plainText[i] = '\0';
            break;
        }
        plainText[i] = (chipText[i] - 'a' + 24) % 26 + 'a';
    }
    cout << plainText << endl;
    return 0;
}

习题1

  1. 求一个4X4的矩阵的对角线元素之和。
  2. 输入一段字符串,然后统计其中包含的大写字母、小写字母、空格、数字的数量。
  3. 打印下面的图案。
      *
     ***
    *****
   *******
  *********
 ***********
*************
 ***********
  *********
   *******
    *****
     ***
      *

习题答案

答案1

#include <iostream>
using namespace std;

int main(){
    int a[4][4];
    for(int i=0;i<4;i++){
        for(int j=0;j<4;j++){
            cin >> a[i][j];
        }
    }
    int sum = 0;
    for(int i=0;i<4;i++){
        for(int j=0;j<4;j++){
            if(i==j || i+j==3){
                sum += a[i][j];
            }
        }
    }
    cout << sum << endl;
    return 0;
}

答案2

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

int main(){
    string ch;
    int res[4] = {0};
    getline(cin, ch);
    for(int i=0;i<ch.length();i++){
        char c = ch[i];
        if(65<=c && c<=90){
            res[0]++;
        }
        else if('a'<=c && c<='z'){
            res[1]++;
        }
        else if(c == ' '){
            res[2]++;
        }
        else if('0'<=c && c<='9'){
            res[3]++;
        }
        else if(c == '\0'){
            break;
        }
    }
    cout << "统计有" << res[0] << 个大写字母 << endl;
    cout << "统计有" << res[1] << 个小写字母 << endl;
    cout << "统计有" << res[2] << 个空格 << endl;
    cout << "统计有" << res[3] << 个数字 << endl;
    return 0;
}

答案3

#include <iostream>
using namespace std;

int main(){
    int n = 1;
    for(int i=0;i<6;i++){
        for(int j=0;j<6-i;j++){
            cout << " ";
        }
        for(int j=0;j<n;j++){
            cout << "*";
        }
        cout << endl;
        n += 2;
    }
    for(int i=0;i<7;i++){
        for(int j=0;j<i;j++){
            cout << " ";
        }
        for(int j=0;j<n;j++){
            cout << "*";
        }
        cout << endl;
        n -= 2;
    }
    return 0;
}

函数

当程序的功能比较多,规模比较大的时候,把所有的程序代码都写在一个主函数中,就会使得主函数变得复杂、头绪不清,使得阅读和维护程序变得困难。此外,有的时候程序需要多次实现某一功能,就需要多次重复编写实现此功能的程序代码,使得程序冗长、不精炼。

模块化程序设计的思路,要求使用实现编好常用的函数来实现不同的功能。事实上,类似printfscanfsqrt等函数都是已经编写好的功能函数。

有一些书本将函数称为方法,含义是一样的。

#include <iostream>
using namespace std;

void print_star(){
    cout << "***************" << endl;
}

void print_message(){
    cout << "Hello world" << endl;
}

int main(){
    print_star();
    print_message();
    print_star();
    return 0;
}

定义函数

定义函数的形式为:

返回值类型名 函数名(参数类型名 参数名, ...){
    函数体
}
  1. 返回值类型名是指调用该函数后返回的值的类型,如果函数不返回任何东西,则为void。
  2. 参数类型名是调用该函数时填入的参数的类型,如果函数不使用任何参数,则留空。

调用函数

调用函数的方法就是使用函数名并添加对应的参数,如果函数具备返回值,还需要设置变量接收函数的返回值。函数的返回值可以当作函数的参数进行嵌套调用。例如:

int c = max(a, b);
int d = max(c, max(a, b));

形式参数与实际参数

在调用函数的时候,主调函数和被调用函数之间含有数据传递过程。其中,定义函数时的参数是形式参数,调用函数时的参数是实际参数

调用函数的时候,实际参数的值传递到形式参数中。函数修改形式参数并不会改变实际参数的值,并且形式参数会在函数执行完毕后销毁。

函数的嵌套使用

C语言对函数的定义是相互平行、独立的,因此函数内部不能再定义函数,但是可以调用函数。函数中调用函数称为函数的嵌套使用。

#include <iostream>
using namespace std;

void printa(){
    cout << "第1句内容" << endl;
}

void printb(){
    cout << "第2句内容" << endl;
}

void printall(){
    printa();
    printb();
    cout << "第3句内容" << endl;
}

int main(){
    printall();
    return 0;
}

函数的递归使用

递归的定义是:自己调用自己。在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。例如:

int f(int x){
    int y,z;
    z=f(y);
    return 2*z;
}

注意到,上面调用函数f的过程中,又需要调用f函数,这是直接调用函数本身。

下面是用递归的方式求阶乘的代码:

#include <iostream>
using namespace std;

int fac(int n){
    int f;
    if(n < 0){
        cout << "n<0, 数据错误" << endl;
    }
    else if(n==0 || n==1){
        f=1;
    }
    else{
        f = fac(n-1)*n;
    }
    return f;
}

int main(){
    int n, y;
    cout << "请输入一个整数" << endl;
    cin >> n;
    y = fac(n);
    cout << n << "!=" << y << endl;
    return 0;
}

Hanoi问题

汉诺塔问题,是一个古典的数学问题。古代有一个梵塔,塔内有3个座A、B、C,开始的时候A上面有64个盘子,盘子大小不相等,大的在下面,小的在上面。有一个老和尚想将这64个盘子从A座搬到C座,但是规定每次移动1个盘子,并且移动过程中任何时候都要保持大盘子在下面,小盘子在上面。移动的过程中可以使用B座,要求写出程序输出移动的步骤。

下面的代码将问题缩减为5个盘子。

#include <iostream>
using namespace std;

void move(char x, char y){
    cout << x << "->" << y << endl;
}

void hanoi(int n, char A, char B, char C){
    if(n == 1){
        move(A, C);
    }
    else{
        hanoi(n-1, A, C, B);
        move(A, C);
        hanoi(n-1, B, A, C);
    }
}

int main(){
    int m;
    cout << "请输入盘子的数量:" << endl;
    cin >> m;
    cout << "移动盘子的步骤是:" << endl;
    hanoi(m, 'A', 'B', 'C');
    return 0;
}

将数组名作为函数的参数

数组名本身是一个指针,所以传递数组作为函数参数实际上传递的是指针的值,也就是数组第一个元素的地址。

例子:

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

float average(float array[], int n){
    float aver, sum = 0.0;
    for(int i=0;i<n;i++){
        sum += array[i];
    }
    aver = sum/n;
    return aver;
}

int main(){
    float score1[5] = {98.5, 97, 91, 60, 55};
    float score2[10] = {67.5, 89.5, 99, 69.5, 77, 89.5, 76.5, 54, 60, 99.5};
    cout << "class A的平均成绩是" << average(score1, 5) << endl;
    cout << "class B的平均成绩是" << average(score2, 10) << endl;
    return 0;
}

如果是二维数组名作为函数参数,则需要指定第二维的长度。

例子:

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

int max_value(int array[][4], int n){
    int max = array[0][0];
    for(int i=0;i<n;i++){
        for(int j=0;j<4;j++){
            if(array[i][j]>max){
                max = array[i][j];
            }
        }
    }
    return max;
}

int main(){
    int a[3][4] = {
        {1,3,5,7},
        {2,4,6,8},
        {3,6,9,12},
        {15,17,18,21}
    };
    int max = max_value(a, 3);
    cout << max << endl;
    return 0;
}

局部变量与全局变量

在一个函数内部定义的变量只能在本函数范围内有效,也就是说,只有在本函数内才能引用它们。在此函数以外是不能使用这些变量的。这样的变量称为局部变量。

而在函数内定义以外的变量是全局变量,全局变量可以在整个程序中任何地方使用。

#include <iostream>
using namespace std;

float Max = 0.0, Min = 0.0;

float average(float array[], int n){
    float aver, sum = 0.0;
    Max = Min = array[0];
    for(int i=0;i<n;i++){
        if(array[i]>Max){
            Max = array[i];
        } else if(array[i]<Min) {
            Min = array[i];
        }
        sum += array[i];
    }
    aver = sum/n;
    return aver;
}

int main(){
    float aver, score[10];
    for(int i=0;i<10;i++){
        cin >> score[i];
    }
    aver = average(score, 10);
    cout << "Max = " << Max << endl;
    cout << "Min = " << Min << endl;
    cout << "Ave = " << aver << endl;
    return 0;
}

结构体

C语言提供了一些由系统定义好的数据类型,例如int、float、char等等。用户可以在程序中用它们定义变量,解决一般的问题。但是人们要处理的问题往往比较复杂,只有系统提供的类型还不能满足应用的要求,C语言允许用户根据需要自己建立一些数据类型,用它定义变量。

例如:

struct Student{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};

上面指定了一个结构体类型,用struct作为声明结构体的关键字。上面的例子中定义了一个结构体类型,它与系统提供的标准类型具有相似的作用,都可以用来定义变量。只不过int等类型是系统已经声明的,而结构体类型是由用户根据需要在程序中指定的。

例子:

#include <iostream>
using namespace std;

struct Person{
    char name[20];
    int count;
}leader[3] = {"Li", 0, "Zhang", 0, "Sun", 0};

int main(){
    char leader_name[20];
    for(int i=1;i<=10;i++){
        cin >> leader_name;
        for(int j=0;j<3;j++){
            if(strcmp(leader_name, leader[j].name) == 0){
                leader[j].count++;
            }
        }
    }
    cout << endl << "Result:" << endl;
    for(int i=0;i<3;i++){
        cout << leader[i].name << ":" << leader[i].count << endl;
    }
    return 0;
}

习题2

  1. 能正确表示逻辑表达式“a大于等于10或者a小于等于零”的语句是:

     A. a>=10 or a<=0
     B. a>=0 | a<=10
     C. a>=10 && a<=0
     D. a>=10 || a<=0
    
  2. 按照C语言的变量命名方式,____符号不能命名变量:

     A. 连接符
     B. 下划线
     C. 大小写字母
     D. 数字
    
  3. 循环语句while(!a)中的表达式等效于:

     A. while(a != 0)
     B. while(a != 1)
     C. while(a == 0)
     D. while(a == 1)
    
  4. 下面程序运行的结果是:

     int a = 4;
     int b = 3;
     int c = a++;
     int d = ++b;
     printf("%d %d\n", a, b, c, d);
    
     A. 4 3 4 4
     B. 5 4 4 4
     C. 4 3 5 4
     D. 5 4 5 4
    
  5. int占用多少字节:

     A. 1
     B. 2
     C. 3
     D. 4
    
  6. 在循环语句中使用break语句的功能是:

     A. 使程序的执行跳出break所在的循环
     B. 使程序结束
     C. 跳出包含此break语句的所有循环
     D. 终止本次循环,进入下一轮循环
    
  7. 元素a[1]代表数组的第几个元素:

     A. 0
     B. 1
     C. 2
     D. 3
    
  8. C语言中,用来跳过循环体后面的语句,然后重新进行下一轮循环的关键字是:

     A. continue
     B. break
     C. switch
     D. case
    
  9. 下面的变量名中,正确的是:

     A. #x
     B. _x
     C. 2x
     D. \x
    
  10. 下面的转义字符中,表示字符串结尾的是:

    A. \n
    B. \\
    C. \t
    D. \0
    
  11. char a[] = "hello,world!"; 其中a数组占用了多少字节:

    A. 12
    B. 13
    C. 14
    D. 不知道
    
  12. 下面与x++等效的语句是:

    A. x += 1
    B. x--
    C. x+-
    D. x -= 1
    
  13. 已知字母A的ASCII码是65,那么字母D的ASCII码是多少?

    A. 65
    B. 66
    C. 67
    D. 68
    
  14. 下面的命令正确的是:

    A. gcc hello.c -0 hello
    B. gcc hello -o hello.c
    C. gcc hello.c -o hello
    D. gcc hello -0 hello.c
    
  15. 下面的引用不正确的是:

    A. #include <stdio.h>
    B. #include <stdlib.h>
    C. #include <stdos.h>
    D. #include <math.h>
    
  16. 下面能够表达20<=x<34的语句是:

    A. 20 <= x < 34
    B. 20 <= x and x < 34
    C. x < 34 && 20 <= x
    D. 20 < x && x<= 34
    
  17. 输入25 13 10,下面程序片段输出的内容是:

    int a, b, c;
    scanf("%d%d%d", &a, &b, &c);
    printf("a+b+c=%d\n", a+b+c);
    
    A. 48
    B. a+b+c=48
    C. a+b+c=48\n
    D. 都有可能
    
  18. 下面的程序中,max的值是:

    int a = 10;
    int b = 15;
    int max = a > b ? 1 : 0;
    
    A. 10
    B. 15
    C. 1
    D. 0
    
  19. 下面的程序中,输出的内容是:

    int a = 2;
    int b = 3;
    int c = 0;
    switch(c){
        case 0 : a++;
        case 1 : b++;
        case 2 : ++a;
        case 3 : ++b;
    }
    printf("a=%d, b=%d\n", a, b);
    
    A. a=3, b=3
    B. a=2, b=3
    C. a=4, b=5
    D. a=4, b=4
    
  20. 下面的语句中,while循环了多少次:

    int k = 0;
    while(k = 1){
        k++;
    }
    
    A. 语法错误,根本跑不起来
    B. 0次
    C. 无限次
    D. 1次
    

队列应用

#include <iostream>
#include <queue>
#include <string>
using namespace std;

void test_empty(){
    queue<int> myqueue;
    int sum = 0;
    for(int i=1;i<=10;i++){
        myqueue.push(i);
    }
    while(!myqueue.empty()){
        sum += myqueue.front();
        myqueue.pop();
    }
    cout<<"total: "<<sum<<endl;
}
//total: 55

void test_pop(){
    queue<int> myqueue;
    int myint;
    cout<<"Please enter some integers (enter 0 to end):\n";
    do{
        cin>>myint;
        myqueue.push(myint);
    }while(myint);
    cout<<"myqueue contains: ";
    while(!myqueue.empty()){
        cout<<" "<<myqueue.front();
        myqueue.pop();
    }
}
/********
Please enter some integers (enter 0 to end):
512
605
420
517
532
0
myqueue contains:  512 605 420 517 532 0
********/

void test_size(){
    queue<int> myints;
    cout<<"0. size: "<<myints.size()<<endl;
    for(int i=0;i<5;i++){
        myints.push(i);
    }
    cout<<"1. size: "<<myints.size()<<endl;
    myints.pop();
    cout<<"2. size: "<<myints.size()<<endl;
}
/****
0. size: 0
1. size: 5
2. size: 4
****/

int main(){
    test_empty();
    cout<<"\n***********************************************\n";
    test_size();
    cout<<"\n***********************************************\n";
    test_pop();
    cout<<"\n***********************************************\n";

    queue<string> q;
    // insert three elements into the queue
    q.push("These ");
    q.push("are ");
    q.push("more than ");
    //cout << "number of elements in the queue: " << q.size()<< endl;

    // read and print two elements from the queue
    cout << q.front();
    q.pop();
    cout << q.front();
    q.pop();
    //cout << "number of elements in the queue: " << q.size()<< endl;

    // insert two new elements
    q.push("four ");
    q.push("words!");
    //cout << "\nnumber of elements in the queue: " << q.size()<< endl;
    // skip one element
    q.pop();

    // read and print two elements
    cout << q.front();
    q.pop();
    cout << q.front() << endl;
    q.pop();

    // print number of elements in the queue
    cout << "number of elements in the queue: " << q.size()<< endl;
}
/*******
*运行结果:
total: 55
***********************************************
0. size: 0
1. size: 5
2. size: 4
***********************************************
Please enter some integers (enter 0 to end):
512
605
420
517
532
0
myqueue contains:  512 605 420 517 532 0
***********************************************
These are four words!
number of elements in the queue: 0
Process returned 0 (0x0)   execution time : 33.512 s
Press any key to continue.
*******/

优先队列应用

#include <iostream>
#include <queue>
#include <string>
using namespace std;

void test_empty(){
    priority_queue<int> mypq;
    int sum = 0;
    for(int i=1;i<=100;i++){
        mypq.push(i);
    }
    while(!mypq.empty()){
        sum += mypq.top();
        mypq.pop();
    }
    cout << "total: " << sum << endl;
}
 
void test_pop(){
    priority_queue<int> mypq;
    mypq.push(30);
    mypq.push(100);
    mypq.push(25);
    mypq.push(40);
    cout << "Popping out elements...";
    while(!mypq.empty()){
        cout << " " << mypq.top();
        mypq.pop();
    }
    cout << endl;
}

void test_top(){
    priority_queue<string> mypq;
    mypq.push("how");
    mypq.push("are");
    mypq.push("you");
    cout << "mypq.top() is now:--->>>   " << mypq.top() << endl;
}

int main(){
    test_empty();
    cout<<"\n***********************************************\n";
    test_pop();
    cout<<"\n***********************************************\n";
    test_top();
    cout<<"\n***********************************************\n";
    priority_queue<float> q;

    // insert three elements into the priority queue
    q.push(66.6);
    q.push(22.2);
    q.push(44.4);

    // read and print two elements
    cout << q.top() << ' ';
    q.pop();
    cout << q.top() << endl;
    q.pop();

    // insert three more elements
    q.push(11.1);
    q.push(55.5);
    q.push(33.3);

    // skip one element
    q.pop();

    // pop and print remaining elements
    while (!q.empty()){
        cout << q.top() << ' ';
        q.pop();
    }
    cout << endl;
}
/******************
运行结果:
total: 5050
***********************************************
Popping out elements... 100 40 30 25
***********************************************
mypq.top() is now:--->>>   you
***********************************************
66.6 44.4
33.3 22.2 11.1
Process returned 0 (0x0)   execution time : 0.055 s
Press any key to continue.
********************/

最小优先队列

#include <iostream>
#include <queue>
#include <vector>
#include <functional>
using namespace std;
 
int main(){
    priority_queue<int, vector<int>, greater<int>> pque;
    pque.push(3);
    pque.push(5);
    pque.push(1);
    pque.push(4);
    while(!pque.empty()){
        cout<<pque.top()<<" ";
        pque.pop();
    }
    cout<<endl;
    return 0;
}
/******************
运行结果:
1 3 4 5
******************/

结构体最小优先队列

#include <iostream>
#include <queue>
#include <string>
using namespace std;

struct package{
    int id;
    string data;
};

bool operator<(package a, package b) {
    return a.id > b.id;
}

int main() {
    priority_queue<package> tmp;
    tmp.push({3,"a"});
    tmp.push({2,"b"});
    int size = tmp.size();
    while(size--) {
        cout << tmp.top().id << " " << tmp.top().data <<endl;
        tmp.pop();
    }
}

/******************
运行结果:
2 b
3 a
******************/

栈式结构应用

#include <iostream>
#include <stack>
#include <vector>
#include <string>
using namespace std;

int main() {
    stack<int,vector<int>> obi;
    for(int i=0;i<10;i++){
        obi.push(i);
    }
    while(obi.size()>0){
        int x = obi.top();
        cout << x << " ";
        obi.pop();
    }
    cout << endl;
    stack<string,vector<string>>obs;
    for(char c='A';c<='Z';c++){
        string s(1, c);
        obs.push(s);
    }
    while(obs.size()){
        string str = obs.top();
        cout << str << " ";
        obs.pop();
    }
    cout<<endl;
    return 0;
}

/******************
运行结果:
9 8 7 6 5 4 3 2 1 0
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
******************/

C/C++ 程序设计常见错误分析

  1. 没有定义变量。

     int main(){
         x = 3;
         y = 6;
         printf("%d\n", x+y);
     }
    
  2. 输入输出的数据类型与用户指定的输入输出格式声明不一致。

     int a = 6;
     float b = 4.5;
     printf("%f, %d\n", a, b);
    
  3. 没有考虑到int、short等数据类型的数值范围。例如short分配2个字节,所以整数范围是-32768至32767。

     int num;
     m = 89101;
     printf("%d", num);
    
  4. 使用输入函数scanf时,忘记用变量的取地址符号&。

     scanf("%d%d", a, b);
    
  5. 输入数据的形式与要求的不符。例如下面的片段,如果输入【3,4】是错误的。应用输入【3 4】,空格可以用回车代替。

     scanf("%d%d", &a, &b);
    
  6. 在用scanf函数向字符数组名输入数据的时候,在数组名前面多加了&。

     char a[20];
     scanf("%s", &a);
    
  7. 在使用scanf函数向数值型数组输入数据时,用数值型数组名。

     int a[20];
     scanf("%d", a);
    
  8. 语句后面漏掉分号。

     a = 3
     b = 4;
    
  9. 预处理语句写错或者是添加分号。

     #include <stdio.h>;
    
  10. 在不该加分号的地方加分号。

    if(a > b);
        printf("this is an error");
    
  11. 对于应该有花括号的复合语句,忘记加花括号。

    sum = 0;
    i = 1;
    while(i<=100)
        sum = sum + i;
        i++;
    
  12. 括号不匹配。

    while((c=getchar()!='#')
        putchar(c);
    
  13. 混淆大小写。

    int main(){
        int A, B, C;
        a = 2;
        b = 3;
        c = a + b;
        printf("%d+%d=%d\n", a, b, c);
    }
    
  14. 把“=”误认为“==”。

    if(score = 100)
        n++;
    
  15. 使用数组元素的时候使用了圆括号。

    int main(){
        int i, a(10);
        for(i=0;i<10;i++){
            scanf("%d", &a(i));
        }
    }
    
  16. 使用数组的时候越界。

    int main(){
        int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        for(int i=1;i<=10;i++)
            printf("%d", a[i]);
    }
    
  17. 二维数组的定义和引用方式不对。

    int main(){
        int a[5,4];
        printf("%d", a[1+2, 2+2]);
    }
    
  18. 误以为数组名代表数组中全部元素。

    int main(){
        int a[4] = {1, 3, 5, 7}, b[4];
        b = a;
    }
    
  19. 混淆字符数组与字符指针的区别。

    int main(){
        char str[4];
        str = "Computer and C";
        printf("%s\n", str);
    }
    
  20. switch语句中漏写break。

    switch(score){
        case 5: printf("Very Good!");
        case 4: printf("Good!");
        case 3: printf("Pass!");
        case 2: printf("Fail!");
        default: printf("data error!");
    }
    

面向对象编程

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

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

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

与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函数返回一个引用常量,不再是左值。

抽象和类

生活中充满复杂性,处理复杂性的方法之一是简化和抽象。人的身体是由无数个原子组成的,而一些学者认为人的思想是由半自主的主体组成的。但将人自己看作一个实体将简单得多。在计算中,为了根据信息与用户之间的接口来表示它,抽象是至关重要的。也就是说,将问题的本质特征抽象出来,并根据特征来描述解决方案。

类是一种将抽象转换为用户定义类型的C++工具,它将数据表示和操纵数据的方法组合成一个整洁的包。

#include <string>
using namespace std;

class Stock{
    private:
        string company;
        long shares;
        double share_val;
        double total_val;
        void set_tot(){
            total_val = shares * share_val;
        }
    public:
        void acquire(const string& co, long n, double pr);
        void buy(long num, double price);
        void sell(long num, double price);
        void update(double price);
        void show();
};

下面的声明创建两个Stock对象,它们分别命名为sally和solly。

Stock sally;
Stock solly;

访问控制

关键字private和public描述了对类成员的访问控制。使用类对象的程序都可以直接访问公有部分,但是只能通过公有函数来访问对象的私有成员。因此,公有成员函数是程序与对象的私有成员之间的桥梁,提供了对象和程序之间的接口,防止程序直接访问数据被称为数据隐藏。C++还提供第三个关键字protected,这里不做阐述。

类设计尽可能将公有接口与实现细节分开。公有接口表示设计的抽象组件。将实现细节放在一起并将它们与抽象分开称为封装。数据隐藏是一种封装,将实现的细节隐藏在私有部分中。

一般的,数据项通常放在私有部分,组成类接口的成员函数放在公有部分。类声明中默认是private访问控制。

实现类成员函数

上面还缺少了一部分,是公有接口的函数实现。定义成员函数时,使用作用域解析运算符来标识函数所属的类。

void Stock::acquire(const string& co, long n, double pr){
    company = co;
    if(n<0){
        cout << "Number of shares can't be negative;";
        cout << company << " shares set to 0" << endl;
        shares = 0;
    } else {
        shares = n;
    }
    share_val = pr;
    set_tot();
}

void Stock::buy(long num, double price){
    if(num<0){
        cout << "Number of shares purchased can't be negative";
        cout << "Transaction is aborted." << endl;
    } else {
        shares += num;
        share_val = price;
        set_tot();
    }
}

void Stock::sell(long num, double price){
    if(num<0){
        cout << "Number of shares purchased can't be negative";
        cout << "Transaction is aborted." << endl;
    } else if(num>shares){
        cout << "You can't sell more than you have!";
        cout << "Transaction is aborted." << endl;
    } else {
        shares -= num;
        share_val = price;
        set_tot();
    }
}

void Stock::update(double price){
    share_val = price;
    set_tot();
}

void Stock::show(){
    cout << "Company: " << company;
    cout << " Shares: " << shares << endl;
    cout << " Shares Price: $" << share_val;
    cout << " Total Worth: $" << total_val << endl;
}

使用类

#include <iostream>
using namespace std;

int main(){
    Stock f;
    f.acquire("NanoSmart", 20, 12.50);
    f.show();
    f.buy(15, 18.125);
    f.show();
    f.sell(400, 20.00);
    f.show();
    f.buy(300000, 40.125);
    f.show();
    f.sell(300000, 0.125);
    f.show();
    return 0;
}

小结

指定类设计的第一步是提供类声明。类声明类似结构声明,可以包括数据成员和函数成员。声明有私有部分,在其中声明的成员只能通过成员函数进行访问。声明还具有公有部分,在其中声明的成员可被使用类对象的程序直接访问。通常,数据成员被放在私有部分中,成员函数被放在公有部分中,因此典型的类声明的格式如下。

class className{
    private:
        data member declarations
    public:
        member function prototypes
};

指定类设计的第二步是实现类成员函数。可以在类声明中提供完整的函数定义,而不是函数原型,但是通常的做法是单独提供函数定义。在这种情况下,需要使用作用域解析运算符来指出成员函数属于哪个类。例如,假设Bozo有一个名为Retort()的成员函数,该函数返回char指针,则其函数头如下:

char* Bozo::Retort()

换言之,Retort()不仅是一个char*类型的函数,而是一个属于Bozo类的char*函数。

构造函数

上面Stock对象不能像初始化int或者结构那样来初始化,原因是数据部分的访问状态是私有的,程序不能直接访问数据成员。我们需要使用构造函数来进行对象的初始化。

声明和定义构造函数

// constructor prototype with some default arguments
Stock(const string& co, long n = 0, double pr = 0.0);

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

使用构造函数

C++在每次创建类对象时,都会使用类构造函数,下面是两种构造函数的调用方式。

  1. 显式地调用构造函数:
Stock food = Stock("Word Cabbage", 250, 1.25);
  1. 隐式地调用构造函数:
Stock garment("Furry Mason", 50, 2.5);

默认构造函数

默认构造函数是在未提供显式初始化值时,用来创建对象的构造函数。如果没有提供任何构造函数,C++将自动提供默认构造函数。例如:

Stock::Stock(){}

对的,它看起来什么都没做。下面是初始化的默认构造函数:

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

析构函数

用构造函数创建对象之后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将自动调用一个特殊的成员函数,该函数的名字叫做析构函数。析构函数完成清理工作,例如对象中使用new分配内存,则析构函数将使用delete来释放这些内存。析构函数的格式:

Stock::~Stock(){}

因为上述的Stock类没有使用new,所以析构函数没有实际要完成的任务。什么时候应该调用析构函数呢?其实这是由编译器决定的,通常不会在代码中显式地调用析构函数。

this指针

this指针指向用来调用成员函数的对象,这样函数调用stock1.topval(stock2)将this设置为stock1对象的地址,使得这个指针可以用于topval方法。下面是topval方法的定义:

const Stock& Stock::topval(const Stock& s) const{
    if(s.total_val > total_val)
        return s;
    else
        return *this;
}

Search

    Table of Contents