指针和地址
一元运算符&可用于取一个对象的地址,因此,下列语句:
p = &c;
将把c的地址赋值给变量p,我们称p为“指向”c的指针。地址运算符&只能应用于内存中的对象,即变量与数组元素。它不能作用于表达式,常量或register类型的变量。
一元运算符*是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。下面是一个简单的例子。
int x = 1,y = 2, z[10];
int *ip; /* ip是指向int类型的指针 */
ip = &x; /* ip指向x */
y = *ip; /* y的值为ip指向的值,即x的值 */
*ip = 0; /* x的值为0 */
ip = &z[0]; /* ip指向z[0] */
指针都必须指向某种特定类型的数据。void类型的指针可以存放指向任何类型的指针,但它不能间接引用其自身。
指针与函数参数
由于C语言是以传值得方式将参数值传递给被调用函数,因此,被调用函数不能直接修改主调函数中变量了值。如果,需要修改变量中的值,可以变量的指针传递给调用函数。如:
void swap(int *px,int *py){
int temp;
temp = *px;
*px = *py;
*py = temp;
}
指针与数组
在C语言中,指针和数组之间的关系十分密切,通过数组下标所能完成的任何操作都可以通过指针来实现。下面是一个例子:
int array[] = {1,2,3,4,5,6,7,8,9,10};
int *pa;
pa = array; /* 等价于pa = &array[0] */
printf("%d\n",*(pa+4)); /* 等价于p[4] */
当把数组名传递给一个函数时,实际上传递的是该数组第一个元素的地址。在函数定义中,形式参数
int arr[];
和
int *arr;
是等价的。通常更习惯于使用后面一种形式,因为它比前者更加直观地表明了该参数是一个指针。
地址算术运算
指针和指针的比较:如果指针p和q指向同一数组的成员,那么它们之间就可以进行类似于==,!=,<,>=的关系比较运算。
指针和整数的加减:
p + n
表示指针p当前指向对象之后第n个对象的地址。在计算p+n时,n将根据P指向的对象的长度按比例缩放,而p指向的对象的长度取决于p的声明。
指针和NULL:指针可以赋值为NULL(0),或和NULL进行比较运算。
字符指针和函数
字符串常量是一个字符数组,例如:
"I am a string"
在字符串得内部表示中,字符数组以空字符'\0'结尾,所以,程序可以通过检查空字符找到字符数组的结尾。字符串常量占据的存储单元数也因此比双引号内的字符数大1。
下面两个定义之间的定义有很大的差别:
char amessage[] = "hello world";
char *pmessage = "hello world";
上述声明中,amessage是一个仅仅足以存放初始化字符串以及空字符'\0'的一维数组。数组中的单个字符可以进行修改,但amessage始终指向同一存储位置。另一方面,pmessage是一个指针,其初值指向一个字符串常量,之后它可以修改以指向其他地址,但如果试图修改字符串得内容,结果是没有定义的(通常会触发段错误)。下面是两个关于字符数组作为参数的例子:
void strcpy(char *s,char *t){
/* 没有考虑到s的空间不够的情况 */
while(*s++ = *t++)
;
}
int strcmp(char *s,char *t){
/* 这里的写法很值得学习 */
for(;*s == *t;s++,t++){
if(*s == '\0'){
return 0;
}
}
return *s - *t;
}
指针数组以及指向指针的指针
由于指针本身也是变量,所以它们也可以像其他变量一样存储在数组中。下面是一个例子:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define MAXLINES 5000
#define MAXLEN 1000
char *lineptr[MAXLINES];
void quicksort(char *lineptr[],int left,int right);
void swap(char *v[],int i,int j){
char *temp = v[i];
v[i] = v[j];
v[j] = temp;
}
int main(int argc, char const *argv[])
{
/* 把数据读到lineptr中 */
int lines;
char buf[MAXLEN];
int len = 0;
while(gets(buf) && len<MAXLINES){
char *s = (char *)malloc(MAXLEN*sizeof(char));
strcpy(s,buf);
lineptr[len++] = s;
}
quicksort(lineptr,0,len-1);
for(int i=0;i<len;i++){
printf(lineptr[i]);
free(lineptr[i]);
}
return 0;
}
void quicksort(char *lineptr[],int left,int right){
/* 递归出口 */
if(left >= right){
return;
}
int pivot = (left + right)/2;
swap(lineptr,pivot,left);
int i,j;/* j指向第一个小于等于pivot的值 */
for(i=left+1,j = left;i<=right;i++){
if(strcmp(lineptr[i],lineptr[left])){
swap(lineptr,++j,i);
}
}
swap(lineptr,left,j);
quicksort(lineptr,left,j-1);
quicksort(lineptr,j+1,right);
}
这个程序是我根据书上的内容改的,可能有一些不好的地方,但是基本功能是实现了的。
多维数组
C语言提供了类似于矩阵的多维数组,但实际上它们并不像指针数组使用得那样广泛。下面以二维数组为例。多维数组的使用和一维数组类似:
int array[10][10];/* 声明一个二维数组 */
array[5][5] = 10; /* 对二维数组的元素赋值 */
值得注意的是,如果将二维数组作为参数传递给函数,那么在函数的参数声明中必须指明数组的列数,如:
void fun(int array[][10]);
该声明还可以写成:
void fun(int *array[10]);
命令行参数
在支持C语言的环境中,可以在程序开始执行时将命令行参数传递给程序。调用主函数main时,它带有两个两个参数。第一个参数(习惯上称argc,用于参数计数)的值表示运行程序时命令行中参数的数目。第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每一个字符串对应一个参数。下面是一个例子,打印出命令行中的参数:
#include <stdio.h>
int main(int argc, char const *argv[])
{
int i;
for(i=1;i<argc;i++){
printf("%s%s",argv[i],i<argc-1?" ":"");
}
printf("\n");
getchar();
return 0;
}
指向函数的指针
在C语言中,函数本身不是变量,但可以定义指向函数的指针。这种类型的指针可以被赋值、存放在数组中、传递给函数以及作为函数的返回值等等。下面看一个例子:
/* 定义函数指针,该函数的返回值是int,参数为两个int */
typedef int (*compare)(int, int);
int max(int a, int b){
if (a > b){
return 1;
}
else if (a < b) {
return -1;
}
else {
return 0;
}
}
int min(int a, int b){
if (a < b) {
return 1;
}
else if (a > b){
return -1;
}
else{
return 0;
}
}
void swap(int *v, int x, int y){
int temp = v[x];
v[x] = v[y];
v[y] = temp;
}
void quicksort(int *v, int left, int right, compare comp){
/* 递归出口 */
if (left >= right){
return;
}
int i, j;
for (i = left + 1, j = left; i <= right; i++){
if (comp(v[i], v[left]) > 0){
swap(v, ++j, i);
}
}
swap(v, left, j);
quicksort(v, left, j - 1, comp);
quicksort(v, j + 1, right, comp);
}
编写完了之后能运行,但我发现函数调用的时候,书上写的是(*comp)(v[i],v[left]),但我发现像我上面那样写也是可以调用起来的。这可能是C编译器做的一些优化吧,从理解上来说,还是书上的比较好。
复杂声明
C语言中的声明有点令人难以理解,主要原因是它是从变量开始,然后按照运算符的优先顺序,进行左右结合。因为不是从左往右或从右往左,所以令人难以理解,下面是几个例子;
/* f先和()结合,*的优先级低于(),说明是一个函数,返回值是int * */
int *f();
/* pf先和*结合,说明是一个指针,然后和()结合,说明是一个函数
*函数的原型为int ();*/
int (*pf)();
/* x先和()结合,说明是一个函数,然后和*结合,说明返回值是一个指针
*然后和[]结合,说明指针指向的是一个数组,然后和()结合
*说明数组的类型是一个函数,然后和char结合,说明函数的返回值是char
*/
char (* (*x())[] )();
网友评论