11 指针
普通指针变量声明: int *p;
11.1 理解指针
在C语言中,指针是一种派生数据类型。它是从C语言的一种基本数据类型创建而来的。指针以内存地址作为其值。由于内存地址表示在计算机内存中保存程序指令和数据的位置,因而可用指针来直接访问和操作存储在内存中的数据。
指针相关的基本概念:
- 计算机的内存地址指的是指针常量(pointer constant)。我们不能修改他们,只能用来存储数据值。
- 我们不能直接保存地址的值,只能利用地址运算符(&),通过保存在该地址中那个的变量来或的该值。这样获得的值称为指针值(pointer value)。指针值(也就是变量的地址)在程序每次运行时都会发生变化。
- 一旦我们有了指针值,就可以把它存储在另一个变量中。包含指针值的变量就成为指针变量(pointer variable)
在C语言中,每个变量都必须声明为某种类型。由于指针变量包含的是存储某种数据类型地址,因此在使用之前必须把它们声明为指针。指针变量的声明如下:
data_type *pt_name;
上面的声明语句告诉了编译器关于变量pt_name的3件事:
- 星号说明变量pt_name是一个指针变量
- pt_name需要一个内存空间
- pt_name是指向data_type类型的变量
指向指针的指针变量声明:int **p1;我们可以使用指向指针的指针来间接地访问目标值
11.4 指针的递增与比例因子
指针可以通过如下方式递增: p= p + 2; p = p + 1; p++;
表示是指针p指向其类型的下一个值。例如,如果p为整形(4个字节)指针,其初始值为2900,那么经过++p后,p的值为2904,而不是2901.也就是说,当指针进行递增时,所增加的值为该指针指向的数据类型的‘长度’。这种长度就成为比例因子。
11.5 指针与数组
C语言指向多维数组的指针:
http://see.xidian.edu.cn/cpp/html/79.html
一维数组中访问元素x[i]的表达式:*(x + i) 或 *(p + i)
二维数组中访问元素x[i][j]的表达式:*(*(x + i) + j)
11.6 指针与字符串
从第8章我们知道字符串可以看做字符数组,声明和初始化:char str[5] = "good";本章学习指针后,我们还可以使用char类型的指针变量来创建字符串:char *str = "good";还可以通过运行时赋值语句:char *string;string = "good";
注意:string = "good";不是字符串复制,因为变量string是一个指针,而不是字符串。(
正如第8章所指出的,C语言不支持通过赋值操作来把一个字符串复制给另一个字符串)
指针的一个重要使用就是处理字符串表。可通过如下方式声明:char name[3][25]; 我们知道,每个字符串的长度很少是等长的。这种声明方式浪费空间,所以我们可以用指针来指向变长的字符串
char *name[3] = {
"New Zealand",
"Australia",
"India"
};
下面语句可以用来显示这3个名字:
for (int i = 0; i < 3; ++i)
{
printf("%s\n", name[i]);
}
要访问第i个名字的第j个字符,可以通过: *(name[i] + j)
注意:*p[3]和(*p)[3]这两种表示法的区别。由于*比[]的优先级更低,*p[3]表示的是把p声明为具有3个指针变量的数组,而(*p)[3]则表示把p声明为指向含有3个元素的数组的指针。
11.7 指向函数的指针
与变量一样,函数也属于某种数据类型,在内存中需要有存储地址。因此可以声明一个指向函数的指针。该指针又可以作为一个参数在另一个函数中使用。声明:type (*fptr) ();该语句告诉编译器,fptr为指向函数的指针,返回type类型的值。用括号把*fptr括起来是必要的。type *gptr();表示把gptr声明为函数,他返回一个指向type类型的指针。
只要把函数名赋给指针,就可以使函数指针指向某个函数。
double mul(int x, int y);
double (*p) ();
p = mul;
函数调用:(*p)(x, y); 等价于 mul(x, y);
/**
功能:使用函数指针作为函数的参数
知识点:指向函数的指针
版本:2014/06/23
*/
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#define PI 3.1415926
double y(double);
double cos(double);
double table (double(*f) (double), double, double, double);
int main(int argc, char const *argv[])
{
printf("Table of y(x) = 2*x*x-x+1\n");
table(y, 0.0, 2.0, 0.5);
printf("\nTable of cos(x) \n");
table(cos, 0.0, PI, 0.5);
system("pause");
return 0;
}
double table (double(*f) (double), double min, double max, double step)
{
double i, value;
for ( i = min; i <= max; i += step)
{
value = (*f)(i);
printf("%5.2f %10.4f\n", i, value );
}
}
double y(double x)
{
return (2*x*x-x+1);
}
兼容性与类型转换
声明为指针的变量不只是一个指针类型的变量。它也是指向某种基本数据类型的指针。因此,总是有一种数据类型与指针关联。我们不能把一种类型的指针赋给另一种类型的指针,尽管两者都是以内存地址作为其值的。这成为指针的不兼容性
所有指针变量存储的都是内存地址,这些内存地址是兼容的,但它们所指向的数据类型是不兼容的。对不同类型的指针不能使用赋值运算符。但利用类型转换运算符,就可以在不兼容的指针类型之间显示的进行赋值操作,这与我们对基本数据类型所做的是一样的。
int x;
char *p;
p = (char *) &x;
char *p;
p = (char *) &x;
在这种情况下,必须确保使用指针p的所有操作都正确的进行了类型转换。
这里有一个例外。这个例外就是空指针(void *)。空指针是通用指针,可以表示任何的指针类型。所有指针类型都可以赋值给空指针,而空指针无需类型转换就可以赋给任意指针。空指针创建: void (*vp);由于空指针没有具体类型,因而不能进行间接引用。
11.8 指针与结构体
struct inventory
{
char name[30];
int number;
float price;
} product[2], *ptr;
把product的第0个元素的地址赋给ptr。
ptr = product;
ptr = product;
指针递增时,使得它指向数组的下一个元素,因此可通过for语句显示product数组成员信息。
for ( ptr = product; ptr < product + 2; ++ptr)
{
printf("%s %d %f\n", ptr->name, (*ptr).number, ptr->price );
}
- 当使用结构体指针时,应小心各种运算符的优先级。
- 当把结构体作为一个参数传递给函数时,一般通过以指向结构体的指针作为传递参数,然后使用指针来操作成员
print_invent (struct invent *item)
{
printf("Name: %s\n", item->name);
}