1、基础概念

    在 c 语言里,基本变量存放的是数据,而指针变量是存放地址的变量(另一变量额地址)。在 c 语言中,指针也称之为地址,所以我们常说某某变量的指针,可以理解为某某变量的在内存中的地址。如:b 变量的指针是 1000,可以理解为  b 在内存中的地址是 1000,注意不能理解为 b 的指针变量是4004(错误的理解)。

2、定义指针变量

    在 c 语言指针的定义形式如下:

类型说明符 *指针变量名

    如:

int *p1,*p2;
 
//错误的定义方式
int* p1,p2;// p1 整形指针,p2 是整形变量

    在 c 里,可以在变量前加 & 符号取得变量的地址,同样地,可以在指针变量前加 * 符号取得指针变量指向的变量的值(该内存地址上存放的数据),如:

int a = 4;
int *p = &a;
printf("指针 p 指向的变量的值是 %d \r\n",*p);// 注意在这行代码里, p 是指针, *p 是指向的变量的值
3、指针变量作为函数参数
//带有指针变量的函数定义形式如下
void fun(int *p1,int *p2)

//调用方式如下:
int *ptr1 = &a1;
int  *ptr2 =  &a2;
fun(ptr1,ptr2)

    注:在被调用函数中,试图修改指针变量的值(即地址)来达到改变实参的目的都是错误的.

    这句话如何理解呢?这里牵涉到一个比较容易混淆的问题-值传递和引用传递的区别.在这里提供一种理解方式,如下:将实参传给形参的时候,形参都是将实参的内容复制一遍,再调用相对应的函数.

void swap1(int num1,int num2){
	int temp = num1;
	num1 = num2;
	num2 = temp;
}
void swap2(int *num1,int *num2){
	// 试图修改指针变量的值(地址)
	int *temp = num1;
	num1 = num2;
	num2 = temp;
}
void swap3(int *num1,int *num2){
	int temp = *num1;
	*num1 = *num2;
        *num2 = temp;
}

int main(){
	int a = 1,b = 2;
	int *ptr1 = &a,*ptr2 = &b;
	swap1(a,b);	// 结果不变:a = 1,b = 2

	swap2(ptr1,ptr2);	// 结果不变,a = 1,b = 2

	swap3(ptr1,ptr2); // a = 2, b =1
}
4、字符串与指针
    字符串是字符的集合,可以使用数组(指针常量)或指针变量来表示。
char *str1 = "tianya";
char str2[] = "tianya";
    
    在上面代码中: str1 是一个指针变量,str2 是指针常量,所以 str2 不能使用 ++ 或 —— 运算符。输入对应字符串的值如下:
printf("str1 的值为 %s \n",str1);
printf("str2 的值为 %s \n",str2);

 

    另外,我们还需要理解一下,下面这几个的定义方式的不同:

char *str = "tianya";
char str[] = "tianya";
char str[10] = "tianya";
char str[2] = "tianya";

5、一维数组和指针
    数组与指针,是一个比较容易让人迷惑的地方,最主要就是各种不同的表示方式所代表的不同的含义,下面我们一步步来理解一下不同的方式:
   1) 定义方式:
int a[5];    // a 其实是一个常量指针,指向数组的第一个元素 a[0]
int *p = a;    // 等效于:int *p = &a[0];

    2) 通过指针引用数组元素

int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("the seconde elem is %d \n",a[1]);//20
printf("the seconde elem is %d \n",*(a+1));//20
printf("the seconde elem is %d \n",p[1]);//20
printf("the seconde elem is %d \n",*(p+1));//20

    类似地推算,我们可以知道:a[i] 等同于 *(a+i),当中 a[0] 等同于 *a 或 *(a+0);*(p+i) 等同于 ptr[i]。看过一些书,会有着这样一种介绍 * 和 [] 等同,,结合前面的例子,我们再看另外一个例子:

int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("the seconde elem is %d \n",a[1]+1);//21
printf("the seconde elem is %d \n",*a+1);//11,可以看成 (*a)+1 或 *(a+0)+1
printf("the seconde elem is %d \n",p[1]+1);//21
printf("the seconde elem is %d \n",*p+1);//11,可以看成 (*p)+1 或 *(p+0)+1

     是不是觉得我们基本可以了解这些使用了,我们再深入地学习一下,当 * 与 ++ 或 —— 相遇时,又会是一种什么样的情况:

// 第一种情况
int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("*p++ = %d \n",*p++);	// 10,可以看成是 *(p++)

// 第二种情况:
int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("*++p = %d \n",*++p);	//20,可以看成 *(++p)

// 第三种情况
int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("++*p = %d \n",++*p);	// 11,可以看成 ++(*p)

    * 与 ++ 或 -- 的运算符优先级相同,结合性是由右往左。如果觉得基本都懂的,那猜猜一下下面这个例子会输出什么?

int a[5] = {10,20,30,40,50};
int *p = a;    // 或 int *p = &a[0];
printf("*p++ = %d \n",*p++);	
printf("*++p = %d \n",*++p);
printf("++*p = %d \n",++*p);

    现在的我们可以来总结一下:

当 p = &a[i] 时,则有:

  • *p++ 相当于 a[i++];
  • *++p 相当于 a[++i];
  • *p--   相当于 a[i--];
  • *—p 相当于 a[—i];


6、二维数组和指针

    在前面,我们已经学过,一维数组的元素值,可以使用 [] 或 * 来获取;类似地,在二维数组里,我们可以使用 ** 、[][] 或 一个 * 与一个 [] 获得,其余的表示法(即一个 * 或 一个 [])只能得到地址。介绍如下:

// 取第二行第一列的地址和值
int a[2][3] = {0,1,2,3,4,5};
printf("address:%p, value:%d \n",&a[1][0],a[1][0]);
printf("address:%p, value:%d \n",a[1]+0,*(a[1]+0));
printf("address:%p, value:%d \n",*(a+1)+1,*(*(a+1)+0));

    在二维数组a里,可以这样取得第 i 行,第 j 列的地址:

&a[i][j] , a[i]+j,*(a+i)+j

    对应的,我们只需要在地址前加一个 * 即可得到其对应的值:

a[i][j],*(a[i]+j),*(*(a+i)+j)

    那现在我们思考一下,应该使用什么样的指针指向二维数组呢??

   指向数组的指针 (*p)[] 指向二维数组了。介绍如下:

int a[2][3]={0,1,2,3,4,5};
int (*p)[3] = a;
	
int i,j;
for( i = 0; i < 2;i ++){
	for(j = 0;j < 3;j++){
		printf("a[%d][%d] = %d  ",i,j,*(*(p+i)+j));
	}
	printf("\n");
}