1.1 C是只能使用标量的语言(标量是指char、int、double和枚举型等数据类型,以及指针。相对地,数组、结构体和共用体这样的多个标量组合的类型,我们称之为聚合类型)
#include <stdio.h>
int main(void)
{
//C是只能使用标量的语言
char str[] = "abc";
printf("%s",str);
/*
不能实现预期的动作,这个表达式通常在比较指针,
而非字符串的内容。字符串其实就是char型数组,不是标量,故不能用"==".
*/
if (str == "abc")
printf("Yes,they are equal");
return 0;
}
如今C(ANSI C)已经能够让我们整合的使用聚合类型了:
①结构体的一次赋值
②将结构体作为函数返回值传递
③将结构体作为函数返回值返回
④auto变量的初始化
结构体可以被整合利用,在实际编程中,应加以利用。如今还经常使用memcry()来进行结构体一次性赋值,其实可以以直接使用结构体一次赋值功能。
而如今的C还不能做到对数组的整合利用。将数组赋值给另外一个数组,或将数组作为参数传递给其他函数等手段,在C中是不存在的。
#include <stdio.h>
int main(void)
{
//无法将数组赋值给另外一个数组 ,或者将数组作为参数传递给其他函数
int a[3] = {1,2,3};
int b[3];
//无法实现数组的一次性赋值
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
//b=a;
printf("%d\n%d\n",a,b);
return 0;
}
1.2 关于指针
K&R:指针是一种保存变量地址的变量。
“指针类型”由其他类型派生而成。因为有了指针类型,所以有了指针类型的变量与指针类型的值。几乎在所有的处理程序中,指针类型的值实际是指内存的地址。
可以指向任何类型的的指针类型:void *
#include <stdio.h>
int main(void)
{
/*指向任何类型的指针*/
int hoge = 5;
void *hoge_p;
hoge_p = &hoge; //不报错
printf("%d",*hoge_p) ; //仅告诉内存地址,,却没有告诉在此地址上保存的数据类型,故不能取出值来,程序报错
printf("%d\n",*(int*)hoge_p) ;//通过将“所指类型不明的指针”hoge_p强制转型成“指向int的指针”,来告知编译器类型信息
return 0;
}
对指针加N,指针前进“当前指针指向的数据类型的长度 x N”。(严格地讲,对于指针加减运算只允许指针指向数组内地元素,或者超过数组长度地下一个元素。指针运算结果也只允许指针指向数组内地元素,以及超过数组长度地下一个元素)
#include <stdio.h>
int main(void)
{
//指针地运算
int hoge;
int *hoge_p;
hoge_p = &hoge;
printf("%p\n",hoge_p) ;
hoge_p++;
printf("%p\n",hoge_p) ;
return 0;
}
1.2.1空指针:
没有指向任何一个对象的指针(由系统保证空指针不指向任何实际的对象或者函数,未指向任何内存空间。没有初始化的指针可能指向任意一处内存,所以指针一般都要在定义时初始化)。通常使用宏定义NULL来表示空指针常量值,不过在C和C++中并不一样。空指针确保它和任何非空指针进行比较都不会相等,因此经常作为函数发生异常时的返回值使用。
1 /* Define NULL pointer value */
2 #ifndef NULL
3 #ifdef __cplusplus
4 #define NULL 0
5 #else /* __cplusplus */
6 #define NULL ((void *)0)
7 #endif /* __cplusplus */
8 #endif /* NULL */
在C中,NULL表示的是指向0的指针,而在C++中,NULL就直接跟0一样了。
在大多数的环境中,空指针就是为0的地址。但是由于硬件状况等原因,世上也许存在值不为0的空指针。有时在获得一个结构体之后,先使用memset()将它的内存区域清零再使用,(C语言提供了动态内存分配函数malloc()和calloc(),但是抱着“清零后比较好”的观点,偏爱calloc()的人很多,这样也许可避免一些难以再现的bug)。使用memset()和calloc()将内存区域清零,其实就是单纯的使用0来填充位。当结构体成员中包含指针的时候,这个指针是否可以作为空指针来使用,最终由运行环境来决定。
NULL、0、'\0':
在C语言中,“当常量0处于应该作为指针使用的上下文中时,它就作为空指针使用”。
通常在C的字符串中使用'\0'结尾。在C语言标准中,空字符定义为“所有的位为0的字节称为空字符”,控字符是值为0的字符。空字符通常使用'\0'表示。因为'\0'为常量,所以它等同于0。
综上所述,当我们要置一个指针为空时,应该用NULL,当我们要给一个字符串添加结束标志时,应该用'\0'。
1.2.2 swap函数
/*常用的几种swap函数*/
#define Swap1(a,b) (a += b, b = a - b, a -= b)
#define Swap2(a,b) {int temp; temp = a;a = b;b = temp;}
#define Swap3(a, b) {((a)^=(b)); ((b)^=(a)); ((a)^=(b));}
void swap(int *a, int *b)
{
int temp;
temp = *a;
*a=*b;
*b = temp;
}
1.3数组和指针的微妙关系
正如之前1.2中所讲:对指针加N,指针前进“当前指针指向的数据类型的长度 x N”。因此,给指向数组的某个元素的指针加N后,指针会指向N个之后的
#include <stdio.h>
int main(void)
{
//数组和指针的微妙关系
int array1[5]={1,2,3,4,5};
int array2[5]={6,7,8,9,10};
int *p;
int i;
for(p = &array1[0];p != &array1[5];p++)
{
printf("%d\n",*p);
}
p = &array2[0];
for(i = 0;i < 5;i++)
{
printf("%d\n",*(p+i));
}
return 0;
}
无论写成p++,还是*(p+i),都不容易阅读。试试最初的例子中的array[i]这样的方式更容易理解。
1.3.1下标运算[]和数组是没有关系的
在上面代码中p = &array1[0]也可以写成下面这样:
p = array1;
另外,程序中 *(p+i) 也可以写成 p[i]。*(p+i) 和 p[i] 是同样的意思,可以认为后者是前者的简便写法。
要点:p[i]是*(p+i)的简单写法,下标运算符[ ]原本只有这种用法,它和数组无关。认为[ ] 和数组没有关系,这里的[ ]是指在表达式中出现的下标运算符[ ]。声明中的[ ],还是表达数组的意思。声明中[ ]和表达式中的[ ]意义完全不同。
1.3.2试图将数组作为函数的参数进行传递
先看下面的一段代码:
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
int get_word(char *buf, int buf_size, FILE *fp)
{
int len;
int ch;
/*跳过读取空白字符*/
/*isalnum(ch)判断字符变量ch是否为字母或数字,定义在ctype.h中定义*/
while ((ch = getc(fp)) != EOF && !isalnum(ch))
;
if (ch == EOF)
return EOF;
/*此时,ch 中保存了单词的初始字符。*/
len = 0;
do {
buf[len] = ch;
len++;
if (len >= buf_size) {
/*由于单词太长,提示错误*/
fprintf(stderr, "word too long.\n");
exit(1);
}
} while ((ch = getc(fp)) != EOF && isalnum(ch));
buf[len] = '\0';
return len;
}
int main(void)
{
char buf[256];
while (get_word(buf, 256, stdin) != EOF) {
printf("<<%s>>\n", buf);
}
return 0;
}
main()中声明的数组 buf,在 get_word()中被填充值。在 main()中,buf 作为函数的参数传递,因为这里是在表达式中,所以 buf 可以解读成“指向数组初始元素的指针”。因此,接受 buf 的 get_word() 才可以像:
int get_word(char *buf, int buf_size, FILE fp)
这样,合法地接受 char *。
一旦在 get_word 中使用下标运算符访问 buf 的内容,倒还真的会让人感觉从 main()传递过来的是 buf 这样的数组。显然这是个错觉,无论如何,从 main()传递过来的是指向 buf 的初始元素的指针。准确地说,在 C 中是不能将数组作为函数参数进行传递的。但是,可以通过传递指向初始元素的指针来达到将数组作为参数进行传递的目的。
要点:如果试图将数组作为函数参数进行传递,那就传递指向初始元素的指针。无论如何都要将数组进行值传递的时候,建议将数组整体整理成结构体成员。
1.3.3 声明函数形参的方法
只有在声明函数形参时,数组的声明才可以被解读成指针(数组形参退化为指针)。
比如,对于
int func(int a[])
编译器可以针对性地解读成:
int func(int *a)
即使定义了元素个数,编译器也是无视的。
必须要引起注意的是,int a[]和 int *a 具有相同意义,在 C 的语法中只有这么一种情况。
要点:在下面声明的形参,都具有相同的意义。
int func(int *a); /写法1*/
int func(int a[]); /*写法2*/
int func(int a[10]); /*写法3*/
写法 2 和写法 3 是写法 1 的语法糖。
ps:编程过程中往往存在如下陷阱:将数组作为函数参数传入,试图在函数中求取数组的长度。这样做是不行的。在将数组作为函数参数传入的同时,往往也会将数组的长度传入。
如下代码:
#include <stdio.h>
int array[5]={1,2,3,4,5};
int * p = array;
int func(int array[])
{
printf("sizeof(array):%d\n",sizeof(array));//数组形参已经退化为指针,故 sizeof(array)所求为指针的大小
printf("sizeof(array[0]):%d\n",sizeof(array[0]));
return sizeof(array)/sizeof(array[0]);//在函数中试图求取数组的长度,将得到错误的结果
}
int main(void)
{
int num1 = 0;
int num2 = 0;
num1 = func(array);
printf("num1 is :%d\n",num1);
num2 = sizeof(array)/sizeof(array[0]);
printf("num2 is :%d\n",num2);
printf("sizeof(array): %d sizeof(array[0]): %d\n", sizeof(array),sizeof(array[0]));
printf("sizeof(p): %d",sizeof(p));
}
输出结果:
sizeof(array):8
sizeof(array[0]):4
num1 is :2
num2 is :5
sizeof(array): 20 sizeof(array[0]): 4
sizeof(p): 8