二维数组

1
int a [3] [5]

代表定义了一个3行5列的二维数组

但a [2] [4]

指的是数组中第3行第5列的那个元素

因为计算机是从0开始数数

多维数组也可借此类推

二维数组的初始化

1
2
3
4
5
6
int a [] [5] = {
{4,5,3,2,5}
{4,7,5,4,6}
}
也可以将元素放在一行,只是不便阅读
int a [] [5] = {4,5,3,2,5,4,7,5,4,6}

列数必须给出,行数可以省略,由编译器来数

每行一个{},逗号分隔

最后的逗号可以存在

如果省略了元素,表示补零

也可以用定位(c99 only)

数组的大小

sizeof可以表示出某个类型或变量在内存中占据的字节数

因此数组的元素个数可以用sizeof(a)/sizeof(a[0])表示

这样表述的好处是,一旦修改数组中初始的数据,不需要再修改遍历的代码

数组的赋值

1
2
int a[] = {1,2,3,4,5};
int b[] = a;

这是不可行的

1
int b[] = int const b[]

b[]被定义后具有了常属性,不能直接赋值给另一个数组

但可以让b数组遍历读入a数组的每一个元素,完成所谓的复制,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int a[4] = {1,2,3,4};
int b[4];
int i;
for ( i=0; i<4; i++ )
{
b[i] = a[i];
}
//遍历数组做赋值
int k;
for ( k=0; k<4; k++ )
{
printf ("%d ",b[k]);
}
//遍历输出b数组

结果正确,但有些人遍历输出时也用了i

1
2
3
4
5
6
7
8
9
10
11
12
{
int a[4] = {1,2,3,4};
int b[4];
int i;
for ( i=0; i<4; i++ )
{
b[i] = a[i];
}
for ( i=0; i<4; i++ )
{
printf ("%d ",b[i]);
}

运行确实没问题,但要指出的是:一专多能是不好的代码

当然也可以用指针,这里不再赘述

遍历

一维数组的遍历可以用一个for循环

1
2
3
for ( i=0; i<3; i++ ) {
a [i] = ...
}

二维数组的遍历则需要用两个for循环

1
2
3
4
5
for ( i=0; i<3; i++ ) {
for (j=0; j<5; j++) {
a [i] [j] = ...
}
}

遍历数组可以做数组的初始化,可以输出数组,可以搜索数组中的元素等等

在遍历时一般用for循环,最好用0和<,而不是1和<=,因为前者遍历时最大的i恰好是数组的最大下标

运算符&

变量的地址

使用&可以获得变量的地址,地址的大小是否与int相同取决于编译器是32位还是64位

1
2
printf("%x",&i);
printf("%p",&i);

所以要输出一个变量的地址时,更应该用%p而不是%x,%x只是把地址当成一个16进制的整数,地址并不总和整数相同

数组的地址

1
2
3
4
5
6
7
8
9
printf("%p\n",&a);
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",&a[1]);
结果为:
0xbff8dd44
0xbff8dd44
0xbff8dd44
0xbff8dd48

从中我们可以看出地址&a == a == a[0],且相邻的元素差4个字节

指针

指针就是保存地址的变量

1
int* p == int *p

两种写法都一样,类型是int,不是int*

1
2
int i;
int* p = &i;

i的地址传给了*p,也就是说p指向了i

指针变量

普通变量的值是实际的值

指针变量的值是具有实际值的地址

作为参数的指针

1
2
3
int i = 0;
void f( int *p );
f( &i );

参数是普通变量的地址

在这个函数内可以通过这个指针访问外面的变量i

运算符“ * ”

*是一个单目运算符,用来访问指针的值所表示的地址上的变量

*P可以做左值和又值

1
2
int k = *p;
*p = k + 1;

数组作为指针

数组变量是特殊的指针,因为数组本身表达地址

因此对于int*p=a,无需用&取地址,但是数组的单元表达的是变量,需要用&取地址

即a == &a[0],指针也可以用[]运算符

下面四种函数原型等价

1
2
3
4
int sum(int *ar,int n);
int sum(int *,int);
int sum(int ar[],int n);
int sum(int [],int);

另外在函数参数中数组相当于指针

* 利用指针交换两个数组

代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main()
{
int a[3]={1,2,3},b[3]={4,5,6},t,i,*p1=a,*p2=b;
for (i=0;i<3;i++)
{
t=*p1;
*p1=*p2;
*p2=t;
p1++;
p2++;
}
for (i=0;i<3;i++)
printf("%d\t",a[i]);
printf("\n");
for (i=0;i<3;i++)
printf("%d\t",b[i]);
return 0;
}

结果正确

字符类型

char

char是一种整数,也是一种特殊的类型:字符。

用单引号表示的字符字面量:’a’,’1’

‘’也是一个字符

printf和scanf里用%c输入字符

字符的输入输出

1
2
3
int char i = '1';
scanf( "%c",&i );得到1
scanf( "%d",&i );得到49

因为’1‘的ASCII编码是49

混合输入

1
2
1.scanf("%d %c",&i,&c);
2.scanf("%d%c",&i,&c);

二者不同,在于2中读取输入的两个值时,如果有空格会读取空格,空格的ASCII码为32

字符计算

1
2
3
4
char c ='A';
c++;
printf("%c\n",c);
结果为B

说明一个字符加一个数字得到ASCII码表中那个数之后的字符

1
2
3
int i ='Z'-'A';
printf("%d\n",i);
结果为25

说明两个字符的减,得到的是它们在表中的距离

大小写转换也可以通过字符计算实现

逃逸字符

用来表达无法印出的控制字符特殊作用的字符

要指出的是回车和换行是两个动作,只是编译器顺带着换行一起执行了

字符串

1
char word[] = {'H','e','l','l','o','!','\0'}

字符串以数组形式存在,以数组或指针的形式访问

\0是结束的标志,它不是字符串的一部分,但它占用一个字节的空间

string.h 里有很多处理字符的函数

两个相邻的字符串常量会相连

1
2
printf("123"
"456")

会输出123456

另外不能用运算符对字符串做运算

”a“就相当于对a做了初始化

字符串常量

字符串初始化后便不可变,字符串在内存中是只读

用指针改变字符串会触发系统的保护机制,引起严重后果

字符串的赋值

1
2
3
char *t = "title";
char *s;
s = t;

这样做只是让指针s指向了t所指的字符串,并没有产生新的字符串

字符串输入输出

1
2
3
char a[8]
scanf("%s",a);
printf("%s",a);

scanf的读入是不安全的,因为不知道要读入的内容的长度

但可以这样:

1
2
3
char a[8]
scanf("%7s",a);
printf("%7s",a);

“7”告诉了scanf最多读7个

两个scanf可以连续读入

常见错误

char*a不能直接用,要先对a初始化,否则出不出错就靠运气了

终于码完了。。。

C语言入门篇完结!!!