C/C++ 语法教程(7)——数组(2)

C/C++ 语法教程(7)——数组(2)

数组进阶

高维数组

上个教程讲解了一位数组的相关使用,但在我们平时的使用中,总还是有需要双下标的情况。

比如矩阵的表示 $a_{ij}$ 就是 $i,j$ 两个变量。

这时我们就需要高维数组

高维数组定义

这里以二维数组为例,可以说与一维数组基本相似,为 TYPE array_name[const_expr1][const_expr2];

下面举几个例子方便大家理解。

const int N=10;
int a[109][109];
double c[100+5][100/2];
char b[N][N][N];

高维数组的使用

也是基本一样,直接调用 a[i][j] 就可以了。

注意每一维都是从 $0$ 开始即可。

高维数组与一维数组的关系

实际上,高维数组的存储也是连续的。

以二维数组为例,顺序大概就是第一维从 $0$ 开始,遍历完第二维再试第一维 $+1$。

如果有二维数组 a[N][N],那么 a[i][j] 其实就对应于一维数组情况下的 a[i*N+j]

数组的实质

一个数组其实就是一个指针,它指向数组第一个元素的地址。

因为数组中的元素的存储地址应该是连续的,所以元素 a[i] 的对应的地址 &a[i],其实也可以表示为 a+i

这也就是说,在读入数组元素时,可以使用 scanf("%d",a+i); 的方法。

同样地,二维数组 a[i][j] 的地址也同样可以表示为 a+i*N+j,其中 $N$ 表示第一维的大小。

数组部分习题

  1. 输入 $n,m,q$,表示即将输入的矩阵的大小 $A_{n \times m},B_{m \times q}$。

    之后 $n$ 行每行输入 $m$ 个数用一个空格相隔开,表示矩阵 $A$ 的元素 $a_{ij}$,同理再输入矩阵 $B$ 的元素 $b_{ij}$。

    我们希望你计算出 $AB$ 的结果,并按照上面的格式输出矩阵乘法的结果。

    样例输入:

    2 2 2
    1 0
    0 1
    2 3
    3 2
    

    样例输出:

    2 3
    3 2
    
  2. 附加题(选做题):(上过陆玫老师的课的同学可以尝试一下)给出 $n$,表示集合 $A$ 的大小,再如上题格式给出 $A$ 上的关系 $R$ 的关系矩阵表示,我们希望输出 $R$ 的传递闭包 $t(R)$ 的关系矩阵表示并输出。(也就是 Warshall 算法的模板)

    样例输入:

    3
    1 1 0
    0 1 1
    0 0 1
    

    样例输出:

    1 1 1
    0 1 1
    0 0 1
    

预处理命令

考虑到一些原因,我们先讲解预处理(预编译)命令。

#define 预处理

常量定义

在前面已经讲过,可以使用 #define macro-name replacement-text 的方法定义常量。

同时 replacement-text 部分可以没有。

比如:

#define PI 3.1415927
#define N 10009
#define debug

参数宏

您可以使用 #define 来定义一个带有参数的宏,比如:

#define MIN(a,b) ((a)<(b)?(a):(b))

这时调用 MIN(x,y) 就会返回 $x,y$ 的较小值。

注意一定要在 replacement-text 也就是后一个的地方将每个变量加上括号,并且最好整个表达式也加上括号。

一般情况

实际上,#define 就像一个查找替换的过程,编译器会在编译开始时完成这些查找替换。

当然对于含有参数的则会保留不变,这也就是为什么上面要求大家一定要加上括号的原因。

如果有下面一段程序:

#include <iostream>
#define sqr(x) (x*x)
int main()
{
    cout<<sqr(2+2)<<endl;
    return 0;
}

它的输出会是 $8$,因为 sqr(2+2) 会被替换为 (2+2*2+2)

简单来说,#define 并不会帮你的参数(表达式)加上括号,所以需要你自己来进行。

宏定义的取消

如果对于一个 #define ,你希望它只对一段程序生效,那么你可以使用 #undef macro-name 语句取消宏定义。

举例来说:

#define sqr(x) ((x)*(x))
//do something...
#undef sqr(x)

这段程序中,只有在 #define 和对应的 #undef 语句之间的程序会进行上面所述的查找替换操作。

条件编译

有时,你的一个程序会希望同时在两个平台(系统)上正常运行,但是在两个系统上你需要引入的头文件,或者使用的函数等有所不同,这时就需要条件编译

有几个指令可以用来有选择地对部分程序源代码进行编译。

这个过程被称为条件编译

#ifdef 相关

我们举例来说,看下面这段代码:

#ifdef debug
    cout<<"test"<<endl;
#endif

其含义就是如果在前面的代码中定义过了符号常量 debug(不能是参数宏),这段代码就会被编译,否则就会被忽略。

比如下面这段中的 cout 就会被编译:

#define debug
//do something...
#ifdef debug
    cout<<"test"<<endl;
#endif

同理与其配套的命令还有:

  1. #ifndef:如果前面未定义过相关的符号常量,就其中的代码会被编译。
  2. #else:配合上述两个语句使用,对应于其不成立编译的代码。

注意无论怎样,都必须要有 #endif

#if 相关

首先介绍一个配合其使用的 defined 方法。

defined(debug) 在定义过了宏 debug 的情况下返回 $1$,否则返回 $0$。

#ifdef 不同的是,defined 不仅能判断符号常量,还可以判断参数宏。

#if 中判断一个宏是否被定义就需要使用 defined,而且 #if 中可以使用 &&||!>< 等等运算符。

比如可以这样使用:

#if defined(VAX) && defined(UNIX) && !defined(DEBUG)
//do something...
#endif

#if CODE_VERSIOM > 2
//do something...
#endif

同理还有 #elif 相当于 else if 的功能。

同时 #ifdef 之后也可以接 #elif

这些命令用法及其丰富,这里只介绍到这里。

### 运算符

# 运算符会把 replacement-text 令牌转换为用双引号引起来的字符串

比如下面这段程序:

#include <iostream>
using namespace std;

#define MKSTR( x ) #x

int main ()
{
    cout << MKSTR(HELLO C++) << endl;

    return 0;
}

就会输出 HELLO C++

因为 MKSTR(HELLO C++) 被编译器替换为了 "HELLO C++"

## 运算符可以连接两个令牌。

比如下面这段程序:

#include <iostream>
using namespace std;

#define concat(a, b) a ## b
int main()
{
   int xy = 100;

   cout << concat(x, y);
   return 0;
}

就会输出 100

因为 concat(x, y) 被编译器替换为了 xy

需要注意的是,这里的 y 即使也是一个变量 y=1,它也只会替换成 xy 而不是 x1

预定义宏

描述
__LINE__这会在程序编译时包含当前行号。
__FILE__这会在程序编译时包含当前文件名。
__DATE__这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。
__TIME__这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。

总结

这些预处理命令看上去纷繁复杂,但实际上大多数都很难用到。

比较常用的只是符号常量、参数宏和少数情况的 #ifdef 语句。

所以如果后面许多内容不是很了解,完全不用担心。

就算真的想要了解,其实也没有很难,这里主要是给大家一个了解的作用。

写教程前也大半不知道的作者开始安慰大家

因为内容不是很重要,就不布置习题了。

 

点赞 0

No Comments

Add your comment