C/C++ 语法教程(7)——数组(2)
Contents
数组进阶
高维数组
上个教程讲解了一位数组的相关使用,但在我们平时的使用中,总还是有需要双下标的情况。
比如矩阵的表示 $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$ 表示第一维的大小。
数组部分习题
- 输入 $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
- 附加题(选做题):(上过陆玫老师的课的同学可以尝试一下)给出 $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
同理与其配套的命令还有:
#ifndef
:如果前面未定义过相关的符号常量,就其中的代码会被编译。#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
语句。
所以如果后面许多内容不是很了解,完全不用担心。
就算真的想要了解,其实也没有很难,这里主要是给大家一个了解的作用。
写教程前也大半不知道的作者开始安慰大家
因为内容不是很重要,就不布置习题了。
No Comments