指针的运算和普通的算术运算是不同的,支持的运算符也很少,有时候你把两个指针相减得到的结果可能会不如你所想。今天来稍微总结一下:
1. 指针的有限算术运算:自增(++), 自减(--), 加上一个整数(+, +=), 减去一个整数(-, -=), 以及减去另一个指针
2. 指针加上或减去一个整数时,并非简单地加上或减去该整数值,而是加上该整数与指针引用的对象的大小的乘积。对象的大小(字节数)取决于对象的数据类型。
3. 对于x=p1-p2,是把从p2到p1的数组元素的个数赋给x。因为除了数组元素外,我们不能认为两个相同类型的变量是在内存中连续存储的,所以指针算术运算除了用于数组外没有什么意义。两个指针相减的结果的类型是ptrdiff_t,它是一种有符号整数类型。
4. 常见的程序设计错误有:
(1)对不指向数组的指针进行算术运算。
(2)把不指向同一数组的两个指针相减或比较。(C和指针P100 讲的比较形象)
(3)指针算术运算的结果超出了数组的范围。
5. 指针类型转换问题:如果两个指针类型相同,那么可以把一个指针赋给另一个指针,否则必须用强制类型转换,把赋值运算符右边的指针的类型转换为赋值运算符左边指针的类型。指向void *类型的指针是例外。任何类型的指针都可以赋给指向void类型的指针,指向void类型的指针也可以赋给任何类型的指针(注:这种情况对于C++就是不对的,参见think in C++,也就是说void* 不能直接赋值给其他类型的指针必须进行强制类型转换)。这两种情况都不需要使用强制类型转换。
先来看一个例子:
#include <stdio.h> #include <stdlib.h> struct A { int a; int b; }; #define offsetof(TYPE,MEMBER) ((size_t)&((TYPE *)0)->MEMBER) #define container_of(ptr,type,member) ( {\ const typeof( ((type*)0)->member ) *__mptr=(ptr);\ (type*)( (char*)__mptr - offsetof(type,member) );} )
int main() { struct A *p = (struct A *)malloc(sizeof(struct A)); p->a = 1; p->b = 2; int *p1 = NULL; p1 = &p->b; //这里的目的是通过算术运算,从p1的地址(p->b的地址)去算出 p->a的地址 printf("p1-4: %x %d\n",((int )p1-sizeof(int)),*(int *)((int )p1-sizeof(int))); //先将指针进行强制类型转换之后,进行算术运算,将得出的结果再转换成指针 printf("P: %x \n",p); printf("P1: %x\n",p1); p1--; printf("p1 suanshu: %x %d\n",p1,*p1); //指针支持的算术运算‘--’ 和 '++' p1++; printf("Container_of p = %x %d\n",container_of(p1,struct A,b),*container_of(p1,struct A,b)); //通过container_of得出整个结构体的指针。 return 0; }
说明:p1-4 打印的地址是:&p->a 也就是 struct *p 的起始地址 (从前两行打印可以看出来);p1的地址是&p->b:440f68;对p1进行‘--’运算使其指向p->a; 通过container_of得出整个结构体的指针。
注:(2012年6月16日20:21:33):以上程序我是在MinGW编译器下运行的,今天发现在微软的编译器下container_of这个宏得不到正确的解析。
在来看一个使用二级指针分配空间的示例程序:
#include <iostream> #include <iterator> #include <algorithm> #include <iomanip> using namespace std; #define out cout<<setw(4) int main() { //int **p = (int **)new int [10]; //这里和下面的方式都可以 int **p = new int *[10]; int aa = 0; for(int i = 0; i < 10 ;i++) p[i] = new int[10]; //由于每次这样分配,每个一维数组的地址内部是连续的,外部不是连续的,所以不能一次memset那么多 //只有每次使用memset设置一个数组,且memset的特性要注意 int bb = 0; int cc = 0xff; int temp = cc; int count = 0; for(int i = 0; i < 10 ;i++) for(int j = 0; j < 10 ; j++) p[i][j] = count++; /*while(aa < 10) { memset(p[aa++],temp,sizeof(int) * 10);//不要乱用,memset每次将一个byte设置为temp值 }*/ cout << "Array internal Address : "<<endl; cout << &p[0][8]<<" "<< p[0][8]<<endl; cout << &p[0][9]<<" "<< p[0][9]<<endl; cout << &p[0][10]<<" "<< p[0][10]<<endl; cout << &p[0][11]<<" "<< p[0][11]<<endl; cout << &p[0][12]<<" "<< p[0][12]<<endl; cout<<"Array external Address: "<<endl; cout << &p[0][0]<<" "<< p[0][0]<<endl; cout << &p[1][0]<<" "<< p[1][0]<<endl; cout << &p[2][0]<<" "<< p[2][0]<<endl; cout<<"For loop: "<<endl; /* //注意和二维数组的区分! for(int i = 1; i <= 100; i++) //这样来打印全部是不行的! { cout<< p[0][i-1] << " "; if(i % 10 == 0 && i != 0) cout << endl; } */ for(int i = 0; i < 10; i++) { for(int j = 0; j < 10 ; j++) { cout<<setw(4) << p[i][j] << " "; } cout << endl; } cout<<"STL Copy: "<<endl; int a = 0; while(a < 10) { copy(*p,*p+10,ostream_iterator<int>(cout," ")); //这里必须使用*p,我开始一直写错,可以参见copy的实现 cout << endl; a++; p++; //这里就注意的每次‘++’就会跳到下一个“数组”的头, '++'直接是跳转到了下一个“对象”引用的位置 } return 0; }
注意:这里虽然使用二级指针分配了一个类似二维数组的空间,请注意和二维数组进行区别。
memset的用法:
void * memset ( void * ptr, int value, size_t num );
Fill block of memory
Sets the first num bytes of the block of memory pointed by ptr to the specified value (interpreted as an unsigned char).
上面程序中我试图使用memset对分配出来的空间进行初始化,但是请注意memset初始化是按字节来的,如果初始化的结果不是0,结果将会非常意外,比如初始化的结果为10,则最后每个元素的值为:0x0a0a0a0a, %d打印的结果为:168430090。而且上面使用二级指针分配的空间并非连续,想要使用memset初始化的话,必须进行多次,也就说按内纯分配的次数来初始化。
附:(copy的实现)
template<class InputIterator, class OutputIterator>
OutputIterator copy ( InputIterator first, InputIterator last, OutputIterator result )
{
while (first!=last) *result++ = *first++;
return result;
}
复杂函数指针:
Think in C++中介绍了一种复杂函数指针的分析方法:
右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。
To define a pointer to a function that has no arguments and no
return value, you say:
void (*funcPtr)();
funcPtr是一个函数指针,该指针指向的函数没有参数,没有返回值。
复杂函数申明:
void * (*(*fp1)(int))[10];
解释:
“fp1 is a pointer to a function that takes an integer argument and returns a pointer to an array of 10 void pointers.”
fp1是一个指向函数的指针,该函数拥有一个int型的参数,返回值是一个指向拥有10元素的指针数组,数组中每个元素都是void * 类型。
float (*(*fp2)(int,int,float))(int);
解释:
fp2 is a pointer to a function that takes three arguments (int, int, and float) and returns a pointer to a function that takes an integer argument and returns a float
fp2是一个指向函数的指针,该函数拥有三个参数,分别是int,int和float类型,返回一个指向函数的指针,该函数拥有一个int型的参数,返回值是float类型。
typedef double (*(*(*fp3)())[10])();
fp3 a;
解释:
fp3 is a pointer to a function that takes no arguments and returns a pointer to an array of 10 pointers to functions that take no arguments and return doubles.
fp3 是一个指向函数的指针,该函数没有参数,返回一个指向拥有十个元素的数组,数组的每个元素是一个函数,这10个函数都没有参数,返回值是double类型。
int (*(*f4())[10])();
解释:
f4 is a function that returns a pointer to an array of 10 pointers to functions that return integers.
f4是一个函数,返回值是一个指向拥有10个元素的数组,每个数组元素是一个函数指针,这些指针指向的函数返回值为int类型。
函数如何返回一个数组? (C编程专家P230)
有了上面的分析可以简单构造为:
int (*func())[20]
func是一个函数,它的返回值是一个指向20个int元素的数组的指针。
函数里面可以定义一个指向20 int 元素的数组的指针:
int (*p)[20];
然后动态分配内存之后,在函数结尾就可以返回了。
注意和这个区别:int *p[20] : p是一个拥有20个元素的数组,数组中的每个元素是一个指向int型的指针。