精华内容
下载资源
问答
  • C语言指针知识

    千次阅读 多人点赞 2019-05-08 21:11:02
    地址与指针 每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。 请看下面的实例,它将输出定义的变量地址: #include <stdio.h> ...

    地址与指针


    每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。
    请看下面的实例,它将输出定义的变量地址:

    #include <stdio.h>
     
    int main ()
    {
       int  var1;
       char var2[10];
     
       printf("var1 变量的地址: %p\n", &var1  );
       printf("var2 变量的地址: %p\n", &var2  );
     
       return 0;
    }
    

    当上面的代码被编译和执行时,它会产生下列结果:

    var1 变量的地址: 0x7fff5cc109d4
    var2 变量的地址: 0x7fff5cc109de
    

    什么是指针?
    指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:

    type *var-name;
    

    在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。用来声明指针的星号 * 与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:

    int    *ip;    /* 一个整型的指针 */
    double *dp;    /* 一个 double 型的指针 */
    float  *fp;    /* 一个浮点型的指针 */
    char   *ch;     /* 一个字符型的指针 */
    

    所有指针的值的实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,都是一样的,都是一个代表内存地址的长的十六进制数。不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

    指针变量的定义与使用

    1.指针变量的定义
    指针变量的定义格式为:
    类型说明符 *指针变量名 [=初值];
    要同时定义两个指针变量:
    int *p1=NULL,*p2=NULL;
    (NULL是c语言中预定义的空指针关键字,它作为一个特殊的指针,表示不指向任何对象)
    定义指针变量时要注意以下几点:
    (1)类型说明符表示该指针变量所指向的变量的数据类型。
    (2)定义指针变量时,指针变量名前必须有一个“ * ”号,表示定义的变量是指针变量。
    (3)指针变量在定义时允许对其初始化。
    例如:
    int a=8;
    int p=&a;
    在这里插入图片描述
    2.指针变量的使用
    两个重要运算符
    (1)&:取地址运算符,如p=&a; 则p为变量a的地址。
    (2)
    :指针运算符,后面只能接指针变量。用于访问指针变量所指向的变量。如:*p表示访问指针变量p所指向的变量的内容。

    int a=8,b;
    int *p;
    p=&a;
    

    对变量a有两种访问方式:
    (1)直接访问。b=a;
    (2)通过指针变量间接访问。b=*p;
    在这里插入图片描述
    变量a与指针变量p的引用

    &a表示变量a的地址,&a=2000
    p指针变量p的值,p=2000=&a
    *p表示指针变量p指向的变量,*p=a=8
    &*p相当于&(*p),&(*p)=&a=2000
    *&a相当于*(&a),*(&a)=*p=a=8
    &p表示指针变量p的地址,&p=2000
    *&p相当于*(&p),*(&p)=2000

    注意:指针是一个地址,而指针变量是存放地址的变量。
    3.指针的运算
    (1)指针和整数的加减运算:
    可以通过指针与整数的加减运算来移动指针p,实现对不同数据单元的访问操作。对不同的数据类型,移动的单位长度不同。单位长度一般是指针所指向的变量的数据类型长度。
    格式一:p=p+n;(或p=p-n;)
    格式二:p++;(或p–;)
    格式三:++p;(或–p;)
    注意:
    指针变量的值加1(或减1),并不是给地址加1(或减1),而是加上(或减去)1个数据类型长度,
    也就是指针所指向的变量在内存中所占的字节数。
    (2)指针和指针的赋值运算:

    int a=10,*p,*q;
    p=&a;
    q=p;
    //p和q的值都是变量a的地址。
    

    4.指向指针的指针
    关系:
    (1)*二级指针变量:代表所指向的以及指针变量。如:*q就代表p;
    (2)**二级指针变量:代表它所指的一级指针变量所指向的变量。如:**q代表a;
    (3)*一级指针变量:代表它所指向的变量。如:*p代表a。


    指针与数组


    一。指向一维数组元素的指针

    c语言数组中的数组元素可以看作相应类型的变量。只要类型匹配,就可以定义一个指针变量,让这个变量存储数组中数组元素的地址,则该指针便指向该数组元素。

    int a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };    //定义a为包含十个数据的数组
    int *p; //p为指向整型变量的指针变量
    p = &a[0]; //把a[0]元素的地址赋给指针变量p
    //p=a;       与p = &a[0]等效,p的值是数组a首元素的地址
    

    通过指针引用一维数组元素:
    1.指向一维数组首元素的指针
    假设指针p指向一维数组a的第一个元素a[0]。则:
    p+1: 使p指向下一个元素a[1].
    p+i: 使p指向元素a[i].
    注意!!!
    (1)可以使用*(p+i)访问元素a[i]。
    (2)因为p和a都表示数组首地址,所以p+i也可以记作a+i,指向元素a[i]。
    (3)指向数组的指针变量也可以带下标,如:p[i]与*(p+i)和*(a+i)等价,表示元素a[i]。
    由此可知:当指针变量p指向一维数组a,即指向一维数组的第一个元素a[0]后,数组的第i+1个元素有以下4种写法:
    a[i] ; p[i] ; *(a+i) ; *(p+i) 。
    a[i]的地址也对应有4种写法:
    &a[i] ; &p[i] ;a+i ; p+i ;
    2.指向一维数组非首元素的指针

    例:

    int a[5],*p;
    p=&a[2];
    //用法较少
    

    举例:
    (1)利用指向数组元素的指针输出数组a的各个元素。

    
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int a[6] = { 11,22,33,44,55,66 };
    	int *p;
    	int i;
    	for (p = a; p < a + 5; p++)
    	{
    		printf("%4d", *p);
    		i++;
    		if (i == 6)
    			break;  //避免指针越界
    	}
    	system("pause");
    	return 0;
    }
    

    (2)字符串复制:实现将字符数组str2中的字符串复制到字符数组str1中。

    // 方法一:用数组名[下标]来访问数组元素
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int i;
    	char str1[20], str2[20] = { "how are you?" };
    	for (i = 0; (str1[i] = str2[i]) != '\0'; i++);
    	puts(str1);
    	system("pause");
    	return 0;
    }
    
    // 方法二:用指针名[下标]来访问数组元素
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int i;
    	char str1[20], str2[20] = { "how are you?" };
    	char *p1 = str1, *p2 = str2;
    	for (i = 0; (p1[i] = p2[i]) != 0; i++);
    	puts(str1);
    	system("pause");
    	return 0;
    }
    
    // 方法三:用指针名加偏移量计算出来的地址来访问数组元素
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int i;
    	char str1[20], str2[20] = { "how are you?" };
    	char *p1 = str1, *p2 = str2;
    	for (i = 0; (*(p1+i) = *(p2+i)) != 0; i++);
    	puts(str1);
    	system("pause");
    	return 0;
    }
    
    // 方法四:用数组名加偏移量计算出来的地址来访问数组元素
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int i;
    	char str1[20], str2[20] = { "how are you?" };
    	for (i = 0; (*(str1+i) = *(str2+i)) != 0; i++);
    	puts(str1);
    	system("pause");
    	return 0;
    }
    

    一维数组中几个关键符号的理解:
    以int buf[100]={0}为例:
    1.buf:两层含义,一是数组名,sizeof(buf)时,就是数组名的含义。二是等价于&buf[0],表示数组第一个元素的首字节地址,是一个常量值。(不能作为左值(作为数组名时,不包括数组的初始化),作为右值时,表示一个地址)
    2.buf[0]:第一个元素的空间,可对其进行读写操作,作为左值被写,作为右值被读。
    3.&buf[0]:等价于buf,是一个地址常量,只能作为右值。
    4.&buf:表示数组首地址,是一个地址常量,只能作为右值。
    buf与&buf的值相等,但含义不同。printf("%p\n",buf)与printf("%p\n",&buf)打印结果相同,表示值相等。printf("%p\n",buf+1)与printf("%p\n",&buf+1)打印结果完全不相同。buf表示数组的第一个元素首字节的地址,加1加的是一个元素空间的大小;&buf表示数组首地址,加1加的是整个数组空间大小,主要用于构造多维数组。

    二。指向二维数组元素的指针
    当指针p指向二维数组中的某个元素后,可以用指针p访问二维数组的所有元素。
    例:

    int a[2][5];
    int *p = &a[0][0];
    

    p指向二维数组a的首元素,如图一。若将数组元素用指针p表示出来,如图二。
    在这里插入图片描述
    假设指针变量p已经指向共有M行N列的数组A的首元素,则:
    A[i][j]的地址是:p+i * N+j ;A[i][j]可表示为:*(p+i * N+j)。
    二维数组a[ ][ ]的有关指针
    注意:&a[i]和a[i]值虽然一样,但&a[i]或a+i指向行,而a[i]或 * (a+1)指向列

    表示形式含义
    a二维数组名,指向一维数组a[0], 即0行起始地址
    a[0],*(a+0),*a0行0列元素的地址
    a+1,&a[1]1行起始地址
    a[1],*(a+1)1行0列元素a[1][0]的地址
    a[1]+2,*(a+1)+2,&a[1][2]1行2列元素a[1][2]的地址
    *(a[1]+2), ((a+1)+2),a[1][2]1行2列元素a[1][2]的值

    例:输出二维数组的有关数据(地址和元素的值)。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int a[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };
    	printf("%d  %d\n\n", a, *a);		//0行起始地址  ,  0行0列元素的地址
    	printf("%d  %d\n\n", a[0], *(a + 0));  //0行0列起始地址
    	printf("%d  %d\n\n", &a[0], &a[0][0]);  //0行起始地址 ,   0行0列元素的地址
    	printf("%d  %d\n\n",a[1],a+1);  //1行0列元素地址 ,   第1行起始地址
    	printf("%d  %d\n\n",&a[1][0],*(a+1)+0);  //1行0列元素地址
    	printf("%d  %d\n\n",a[2],*(a+2));  // 2行0列元素地址
    	printf("%d  %d\n\n",&a[2],a+2); //2行起始地址 
    	printf("%d  %d\n\n",a[1][0],*(*(a+1)+0));   //1行0列元素的值
    	printf("%d  %d\n\n",*a[2],*(*(a + 2) + 0));  //2行0列元素的值
    	system("pause");
    	return 0;
    }
    
    

    运行结果:
    在这里插入图片描述

    例:用指针法求二维数组中的最大值。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int a[2][5], max, i, j;
    	int *p = &a[0][0];
    	printf("请输入数组中的各元素\n");
    	for (i = 0; i < 2; i++)
    	{
    		for (j = 0; j < 5; j++)
    		{
    			scanf("%d", p++);		//通过p++依次引用各数组元素的地址
    		}
    	}
    	max = a[0][0];
    	p = &a[0][0];
    	for (i = 0; i < 2; i++)
    	{
    		for (j = 0; j < 5; j++)
    		{
    			if (max < *(p + i * 5 + j))
    				max = *(p + i * 5 + j);		//max总是存放比较大的数
    		}
    	}
    	for (i = 0; i < 2; i++)
    	{
    		for (j = 0; j < 5; j++)
    		{
    			printf("%5d", *(p + i * 5 + j));	//输出数组
    		}
    	}
    	printf("\n数组中的最大值为:%d\n", max);
    	system("pause");
    	return 0;
    }
    

    上述例子为指针指向整型变量,下面给出例子为:指针指向一个包含m个元素的一维数组。

    //输出二维数组任一行任一列元素的值。
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	int a[3][4] = { 1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23 };		//定义二维数组并初始化
    	int(*p)[4], i, j;  //指针变量p是指向包含4个整型元素的一维数组
    	p = a;		//p指向二维数组的0行
    	printf("输入要求输出的行号和列号:\n");
    	scanf("%d%d", &i, &j);
    	printf("a[%d][%d]=%d\n", i, j, *(*(p + i) + j));		//输出a[i][j]的值
    	system("pause");
    	return 0;
    }
    

    三.指向数组首元素的指针变量的运算
    若指针变量p指向数组a的首元素,则:
    (1)p++(或p+=1)
    p指向下一个元素。
    (2)*p++
    相当于 *(p++)。都是先取 *p 的值,然后使p加1。
    (3) *(p++)与 *(++p)
    *(p++)同 *p++,先取 *p 的值,然后使p加1;
    *(++p)先使p加1,然后取 *p 的值。
    若p初值为a(即&a[0]),若输出 *(p++),得到a[0]的值,若输出 *(++p),得到a[1]的值。
    (4)(*p)++
    表示p指向的元素值加1。相当于(a[0]++)。
    (5)如果指针指向数组a的非首元素a[i]。则:

    • *(p–)相当于a[i–],先取 *p,再使p减1.
    • *(p++)相当于a[++i],先使p+1,在取 *p.
    • *(–p)相当于a[–i],先使p-1,在取 *p.

    指针数组和数组指针的区别:

    指针数组
    指针数组:指针数组可以说成是”指针的数组”,首先这个变量是一个数组。
    其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型。
    在 32 位系统中,指针占四个字节。
    数组指针
    数组指针:数组指针可以说成是”数组的指针”,首先这个变量是一个指针。
    其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。

    指针数组的例题:

    //将若干字符串按字母顺序(由小到大)输出
    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    void sort(char *name[], int n)		//对字符串排序
    {
    	char *t;
    	int i, j, k;
    	for (i = 0; i < n - 1; i++)		//	用选择法排序
    	{
    		k = i;
    		for (j = i+1; j < n; j++)
    		{
    			if (strcmp(name[k], name[j])>0)
    				k = j;
    		}
    		if (k != i)
    		{
    			t = name[i];
    			name[i] = name[k];
    			name[k] = t;
    		}
    	}
    }
    void print(char *name[], int n)		//打印
    {
    	int i;
    	for (i = 0; i < n; i++)
    	{
    		printf("%s\n", name[i]);
    	}
    }
    int main()
    {
    	char *name[] = { "Follow me", "BASIC", "Great Wall", "FOREVER", "Computer design" };		//指针数组的值为各字符串的首字符的地址
    	int n = 5;
    	sort(name, n);		//实参为数组首元素的地址,形参为指针数组名
    	print(name, n);
    	system("pause");
    	return 0;
    }
    
    

    *指针的一些复杂说明:

    1. int p; 这是一个普通的整型变量
    2. int *p 首先从 p 处开始,先与*结合,所以说明 p 是一个指针, 然后再与 int 结合, 说明指针所指向的内容的类型为 int 型。所以 p 是一个返回整型数据的指针。
    3. int p[3] – 首先从 p 处开始,先与[] 结合,说明 p 是一个数组, 然后与 int 结合, 说明数组里的元素是整型的, 所以 p 是一个由整型数据组成的数组。
    4. int *p[3]首先从 p 处开始, 先与 [] 结合, 因为其优先级比 * 高,所以 p 是一个数组, 然后再与 * 结合, 说明数组里的元素是指针类型, 然后再与 int 结合, 说明指针所指向的内容的类型是整型的, 所以 p 是一个由返回整型数据的指针所组成的数组。(指针数组)
    5. int (*p)[3] 首先从 p 处开始, 先与 * 结合,说明 p 是一个指针然后再与 [] 结合(与"()"这步可以忽略,只是为了改变优先级), 说明指针所指向的内容是一个数组, 然后再与int 结合, 说明数组里的元素是整型的。所以 p 是一个指向由整型数据组成的数组的指针。(数组指针)
    6. int **p 首先从 p 开始, 先与 * 结合, 说是 p 是一个指针, 然后再与 * 结合, 说明指针所指向的元素是指针, 然后再与 int 结合, 说明该指针所指向的元素是整型数据。由于二级指针以及更高级的指针极少用在复杂的类型中, 所以后面更复杂的类型我们就不考虑多级指针了, 最多只考虑一级指针。
    7. int p(int) 从 p 处起,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里分析, 说明该函数有一个整型变量的参数, 然后再与外面的 int 结合, 说明函数的返回值是一个整型数据。
    8. int (*p)(int)从 p 处开始, 先与指针结合, 说明 p 是一个指针, 然后与()结合, 说明指针指向的是一个函数, 然后再与()里的 int 结合, 说明函数有一个int 型的参数, 再与最外层的 int 结合, 说明函数的返回类型是整型, 所以 p 是一个指向有一个整型参数且返回类型为整型的函数的指针。
    9. int *(*p(int))[3] 可以先跳过, 不看这个类型, 过于复杂从 p 开始,先与 () 结合, 说明 p 是一个函数, 然后进入 () 里面, 与 int 结合, 说明函数有一个整型变量参数, 然后再与外面的 * 结合, 说明函数返回的是一个指针, 然后到最外面一层, 先与[]结合, 说明返回的指针指向的是一个数组, 然后再与 * 结合, 说明数组里的元素是指针, 然后再与 int 结合, 说明指针指向的内容是整型数据。所以 p 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数。

    指针与函数


    一。指针做函数参数
    指针可以作为参数在调用函数和被调用函数之间传递数据,传递的是“地址值”。

    如果有一个实参数组,要想改变此数组中元素的值,实参与形参的对应关系有四种:

    • 形参和实参都用数组名。(不举例)
    • 实参用数组名,形参用指针变量。
    • 实参为指针变量,形参用数组名。
    • 形参和实参都用指针变量。(实参中指针指向数组)

    1.函数形参为指针,实参为地址表达式
    例:利用指针做参数,交换两个变量的值。

    #include<stdio.h>
    #include<stdlib.h>
    void swap1(int *a, int *b)
    {
    	int c;
    	c = *a;	//*为解引用操作
    	*a = *b;
    	*b = c;
    }
    
    int main()
    {
    	int a = 10, b = 100;
    	swap1(&a, &b);
    	printf("调用swap1函数后,主函数中a和b的值为%d  %d\n", a, b);
    	system("pause");
    	return 0;
    }
    

    执行结果为:
    在这里插入图片描述

    #include<stdio.h>
    #include<stdlib.h>
    void swap2(int *a, int *b)
    {
    	int *c;
    	c = a;
    	a = b;
    	b = c;
    }
    int main()
    {
    	int a = 10, b = 100;
    	swap2(&a, &b);
    	printf("调用swap2函数后,主函数中a和b的值为%d  %d\n", a, b);
    	system("pause");
    	return 0;
    }
    

    执行结果为:
    在这里插入图片描述
    分析:
    很明显,swap1函数正确,两个变量值得到了交换,而swap2函数调用后两个值并没有交换。
    因为swap2函数虽然经过了一系列的变换,但是地址中的内容没有变。

    不能企图通过改变指针形参的值而使指针实参的值而改变。

    2.函数形参为指针,实参为数组名
    例:将数组a中的n个整数按相反顺序存放。

    #include<stdio.h>
    #include<stdlib.h>
    void fun(int *x, int n)		//形参为指针
    {
    	int *p, *i, *j;
    	int temp,m;
    	m = (n - 1) / 2;
    	j = x + n - 1;
    	p = x + m;  //p指向a[m]的地址
    	for (i = x; i <= p; i++, j--)
    	{
    		temp = *i;  //交换a[i]和a[j]
    		*i = *j;
    		*j = temp;
    	}
    }
    int main()
    {
    	int i, a[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    	printf("原顺序为:\n");
    	for (i = 0; i < 10; i++)
    	{
    		printf("%4d", a[i]);
    	}
    	printf("\n");
    	fun(a, 10);		//实参为数组名
    	printf("排序后为:\n");
    	for (i = 0; i < 10; i++)
    	{
    		printf("%4d", a[i]);
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    

    运行结果为:
    在这里插入图片描述

    • 用指针做形参,数组名做实参,有两种方法:
    • 用指向变量的指针变量
    • 用指向一维数组的指针变量。
    • 举例如下:
    //1.有3个学生,各学4门课,计算总平均成绩,第n个学生的学习成绩
    #include<stdio.h>
    #include<stdlib.h>
    void average(float *p, int n)     //求平均成绩的函数
    {
    	float *p1;	
    	float sum = 0, aver;
    	p1 = p + n - 1;		//p1指向最后一个元素
    	for (p; p <= p1; p++)		//使p先后指向二维数组的各个元素
    	{
    		sum = sum + (*p);
    	}
    	aver = sum / n;
    	printf("总平均成绩为:%.2f\n", aver);
    }
    
    void search(float(*p)[4], int n)     //p是指向具有四个元素的一维数组的指针
    {
    	int i;
    	for (i = 0; i < 4; i++)
    	{
    		printf("%.2f  ", *(*(p + n) + i));
    	}
    	printf("\n");
    }
    int main()
    {
    	int x;
    	float score[3][4] = { { 65, 67, 70, 60 }, { 80, 87, 90, 81 }, { 90, 99, 100, 98 } };
    	average(*score, 12);  //12个成绩的平均分。  实参: *score=score[0]=&score[0][0]
    	
    	scanf("%d", &x);
    	printf("输出第%d个学生的成绩:\n", x);
    	search(score, x);		//实参:score代表该数组第0行起始地址
    	system("pause");
    	return 0;
    }
    

    运行结果为:
    在这里插入图片描述

    //2.在题1的基础上,查找有一门以上课程不及格的学生,输出他们全部课程的成绩。
    #include<stdio.h>
    #include<stdlib.h>
    void search(float(*p)[4], int n)		 //形参:指针p指向包含4个元素的一维数组
    {
    	int i, j, flag;
    	for (j = 0; j < n; j++)
    	{
    		flag = 0;
    		for (i = 0; i < 4; i++)
    		{
    			if (*(*(p + j) + i) < 60)		//p+j为score数组第j行的起始地址,*(p + j)=&score[j][0]  ,*(p + j)+i=&score[j][i]。
    				flag = 1;
    		}
    		if (flag == 1)
    		{
    			printf("第%d个学生不及格,他的成绩为:\n", j + 1);
    			for (i = 0; i < 4; i++)
    			{
    				printf("%.2f  ", *(*(p + j) + i));		//*(*(p + j) + i)就是score[j][i]的值
    			}
    			printf("\n");
    		}
    	}
    }
    int main()
    {
    	float score[3][4] = { { 65, 57, 70, 60 }, { 50, 87, 90, 81 }, { 90, 99, 100, 98 } };
    	search(score, 3);
    	system("pause");
    	return 0;
    }
    

    结果为:在这里插入图片描述

    3.实参为指针变量,形参用数组名。
    举例:

    //对10个整数由从大到小排序
    #include<stdio.h>
    #include<stdlib.h>
    void fun(int x[], int n)		//形参为数组名
    {
    	int i, j, k, t;
    	for (i = 0; i < n - 1; i++)
    	{
    		k = i;				    //选择排序
    		for (j = i + 1; j < n; j++)
    		{
    			if (x[j]>x[k])
    				k = j;
    		}
    		if (k != i)
    		{
    			t = x[i];
    			x[i] = x[k];
    			x[k] = t;
    		}
    	}
    }
    int main()
    {
    	int i, a[10], *p;
    	p = a;
    	printf("请输入10个数\n");
    	for (i = 0; i < 10; i++)
    	{
    		scanf("%4d", p++);
    	}
    	p = a;				//很重要!!使指针重新指向a[0]
    	fun(p, 10);		//实参为指针
    	printf("排序后的数为:\n");
    	for (p = a, i = 0; i < 10; i++)
    	{
    		printf("%4d", *p);
    		p++;
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    

    4.实参和形参都为指针变量
    举例:

    //对10个整数由从大到小排序
    #include<stdio.h>
    #include<stdlib.h>
    void fun(int *x, int n)		//形参为指针
    {
    	int i,j,k,t;
    	for (i = 0; i <n-1; i++)
    	{
    		k = i;				    //选择排序
    		for (j = i + 1; j < n; j++)
    		{
    			if (*(x+j)>*(x+k))
    				k = j;
    		}
    		if (k != i)
    		{
    			t = *(x+i);
    			*(x + i) = *(x + k);
    			*(x + k) = t;
    		}
    	}
    }
    int main()
    {
    	int i, a[10], *p;
    	p = a;
    	printf("请输入10个数\n");
    	for (i = 0; i < 10; i++)
    	{
    		scanf("%4d", p++);
    	}
    	p = a;				//很重要!!使指针重新指向a[0]
    	fun(p, 10);		//实参为指针
    	printf("排序后的数为:\n");
    	for (p = a, i = 0; i < 10; i++)
    	{
    		printf("%4d", *p);
    		p++;
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    

    函数指针与指针函数

    一。函数指针:

    1.定义:

    函数指针是指向函数的指针变量,本质上是一个指针变量,表示的是一个指针,它指向的是一个函数,其形式一般如下:

    类型说明符 (*函数名)(参数)

    例如,int(*p)(int ,int);
    定义p是一个指针,指向函数类型为整型且有两个整型参数的函数。
    此时指针p的类型用:int( * )(int ,int)来表示。
    2.用函数指针调用函数
    (1)通过函数名调用函数。

    //输出a和b中的最大值。
    #include<stdio.h>
    #include<stdlib.h>
    int max(int x, int y)
    {
    	return((x > y) ? x : y);
    }
    int main()
    {
    	int a, b, c;
    	printf("输入a和b的值\n");
    	scanf("%d%d", &a, &b);
    	c = max(a, b);
    	printf("最大值为:%d", c);
    	system("pause");
    	return 0;
    }
    

    (2)通过指针调用它所指向的函数

    //输出a和b中的最大值。
    #include<stdio.h>
    #include<stdlib.h>
    int max(int x, int y)
    {
    	return((x > y) ? x : y);
    }
    int main()
    {
    	int(*p)(int, int);		//定义函数指针
    	p = max;
    	int a, b, c;
    	printf("输入a和b的值\n");
    	scanf("%d%d", &a, &b);
    	c =(*p)(a, b);
    	printf("最大值为:%d", c);
    	system("pause");
    	return 0;
    }
    

    3。用函数指针做函数参数

    原理简述:

    有一个函数(fun),有两个形参(x1和x2),定义x1和x2为指向函数的指针。在调用函数fun时,实参为两个函数名f1,f2,给形参传递的是函数f1和f2的入口地址,这样在fun中就可以调用f1和f2函数了。

    void fun(int(*x1)(int), int(*x2)(int, int))	    //形参为函数指针(f1,f2)
    {
    	int a, b, i = 3,j = 5;
    	a = (*x1)(i);			//调用f1函数,i是形参
    	b = (*x2)(i, j);		//调用f1,f2函数,i,j是形参
    }
    

    例题:
    有两个整数a和b,由用户输入1,2,3其中一个值。如输入1,则程序给出最大值,如输入2,则给出最小值,如输入3,则求两数之和。

    #include<stdio.h>
    #include<stdlib.h>
    int fun(int x, int y, int(*p)(int, int))			//形参为函数指针
    {
    	int result;
    	result = (*p)(x, y);
    	printf("%d\n", result);		//输出结果
    	return(result);
    }
    int max(int x, int y)
    {
    	printf("最大值为:\n");
    	return((x > y) ? x : y);
    }
    int min(int x, int y)
    {
    	printf("最小值为:\n");
    	return((x < y) ? x : y);
    }
    int add(int x, int y)
    {
    	int z; 
    	z = x + y;
    	printf("和为:\n");
    	return (z);
    }
    int main()
    {
    	int a = 100, b = -10,n;
    	printf("请输入1或2或3:\n");
    	scanf("%d", &n);
    	if (n == 1)
    		fun(a, b, max);
    	else if (n == 2)
    		fun(a, b, min);
    	else if (n == 3)
    		fun(a, b, add);
    	else
    		printf("请重新输入。");
    	system("pause");
    	return 0;
    }
    
    
    

    二。指针函数

    1.定义:
    是指带指针的函数,本质上是一个函数,函数返回类型是某一类型的指针,
    其形式一般如下所示:

    类型标识符 *函数名(参数列表)

    例如:
    int *max(int x,int y);
    函数名为max,其返回值为指针,指向整型数据。

    例题:

    //1.有a个学生,每个学生有b门课程的成绩。要求用户输入学号后,输出该学生的全部成绩。
    //用指针函数来实现。
    
    #include<stdio.h>
    #include<stdlib.h>
    float *search(float(*pointer)[4], int n)		//search为指针函数,  形参pointer是指向一维数组的指针变量
    {
    	float *pt;
    	pt = *(pointer + n);		//pt的值为:&score[k][0]
    	return(pt);
    }
    int main()
    {
    	float score[][4] = { { 65, 57, 70, 60 }, { 50, 87, 90, 81 }, { 90, 99, 100, 98 } };
    	float *p;
    	int i, k;
    	printf("输入你要找的学生学号:\n");
    	scanf("%d", &k);
    	printf("学生%d的成绩为:\n", k);
    	p = search(score, k-1);  //实参为数组首行地址, 返回score[k][0]的地址
    	for (i = 0; i < 4; i++)
    	{
    		printf("%.2f\t", *(p + i));		//输出score[k][0]——score[k][3]的值。
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    

    结果为:
    在这里插入图片描述

    //在第一题的基础上,找出有不及格的成绩的学生和学号。
    #include<stdio.h>
    #include<stdlib.h>
    float *search(float(*pointer)[4])		//search为指针函数,  形参pointer是指向一维数组的指针变量
    {
    	int i;
    	float *pt;
    	pt =NULL;		//初始化
    	for (i = 0; i < 4; i++)
    	{
    		if (*(*pointer + i) < 60)
    			pt = *pointer;				//有不及格,使pt指向score[i][0]
    	}
    	return(pt);
    }
    int main()
    {
    	float score[][4] = { { 65, 57, 70, 60 }, { 50, 87, 90, 81 }, { 90, 99, 100, 98 } };
    	float *p;
    	int i, j;
    	for (i = 0; i < 3; i++)
    	{
    		p = search(score + i);	//调用search函数,如果有不及格则返回score[i][0]的地址,否则返回NULL.
    		if (p == *(score + i))		//如果返回score[i][0]的地址,则p不为空。
    		{
    			printf("学生%d的成绩为:\n", i+1);
    			for (j = 0; j < 4; j++)
    			{
    				printf("%.2f\t", *(p + j));		//输出score[i][0]——score[i][3]的值。
    			}
    			printf("\n");
    		}
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    

    结果为:
    在这里插入图片描述


    指针与字符串


    1.使指针变量指向字符串的方法
    (1)在定义的同时给指针变量赋初值。
    char *p=“Gold medal”;
    (2)给指针变量赋值。
    char *p;
    p=“Gold medal”;

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	char *p = "Gold medal";
    	printf("%s", p);
    	system("pause");
    	return 0;
    }
    
    
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	char *p = "Gold medal";
    	while (*p != '\0')
    	{
    		printf("%c", *p++);
    	}
    	system("pause");
    	return 0;
    }
    
    

    都是将字符串的第一个字符的地址赋给指针变量p。
    C语言对字符串常量是按字符数组处理的,在内存中开辟一个字符数组来存放字符串常量(包括字符串结束符‘\0’),程序在定义字符指针变量p时只是把字符串的首地址赋给p,而不能把整个字符串赋给p。
    2.指向字符串常量的指针变量的使用
    (1)把字符串当作整体来处理

    • scanf("%s",指针变量);
    • gets(指针变量);
    • printf("%s",指针变量);
    • puts(指针变量);

    (2)处理字符串中的单个字符
    若指针已经指向了字符串常量,则用指针变量表示的第i个字符为:
    *(指针变量+i)
    3.字符串中字符的存取方式

    • 下标方法
    • 指针方法
      例题:
      将字符串a复制为字符串b,然后输出字符串b。(两种方法)
    //1。数组的方法
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	char a[] = "I am a student.", b[20];  //定义字符数组
    	int i;
    	for (i = 0; *(a + i) != '\0'; i++)
    	{
    		*(b + i) = *(a + i);		//将a[i]的值赋给b[i]
    	}
    	*(b + i) = '\0';		//数组b的有效字符后加'\0'
    	printf("字符串a是:%s\n", a);
    	printf("字符串b是:\n");
    	for (i = 0; b[i] != '\0'; i++)
    	{
    		printf("%c", b[i]);		//逐个输出字符
    	}
    	printf("\n");
    	system("pause");
    	return 0;
    }
    
    //2。指针的方法
    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	char a[] = "I am a student.", b[20];  //定义字符数组
    	char *p1, *p2;
    	p1 = a; p2 = b;		//指针分别指向数组的第一个元素
    	for (p1; *p1 != '\0'; p1++,p2++)
    	{
    		*p2 = *p1;		//将p1所指向的元素的值赋给p2所指向的元素
    	}
    	*p2 = '\0';		//有效字符后加'\0'
    	printf("字符串a是:%s\n", a);
    	printf("字符串b是:%s\n", b);
    	system("pause");
    	return 0;
    }
    

    4.使用字符指针变量与字符数组的区别
    (1)存储内容不同:
    字符指针变量本身是一个变量,用于存放字符串的首地址,编译时只为其分配一个存储单元。而字符串本身就是存放在以该首地址为首的一块连续内存空间中并以‘\0’作为结束标志。
    字符数组是由若干数组元素组成,每个元素中放一个字符,编译时为其分配若干存储单元
    (2)赋值方法不同:
    对于字符指针变量,可用以下方法赋值:
    char *p;
    p=“Gold medal”;
    而字符数组只能对各个元素逐个赋值,不能对数组名赋值。

    char p[20];
    p = "Gold medal";
    //该种赋值方法错误
    

    (3)指针变量的值是可以改变的。
    而数组名虽是地址,但它的值是不能改变的。

    #include<stdio.h>
    #include<stdlib.h>
    int main()
    {
    	char *p ="Gold medal";
    	p = p + 5;
    	printf("%s", p);
    	system("pause");
    	return 0;
    }
    //运行结果为:
    medal
    
    char p[] ="Gold medal";
    p = p + 5;
    printf("%s", p);
    //是错误的
    

    (4)输入字符串时有区别;
    对于字符指针变量:

    	char *p;
    	scanf("%s", p);
    	//是不可以的。但是可以这样输入:
    	
    	char str[20];
    	char *p=str;  //使p指向了确定内存
    	scanf("%s", p);  
    

    对于字符数组:

    char p[10];
    scanf("%s", p);
    //是可以的
    

    5.字符指针做函数参数
    在被调函数中可以改变字符串的内容,在主调函数中可以引用改变后的字符串。
    (1)用字符数组名作为函数参数。

    //用函数调用实现字符串的复制。
    #include<stdio.h>
    #include<stdlib.h>
    void copy(char from[], char to[])		//形参为字符数组
    {
    	int i = 0;
    	while (from[i] != '\0')
    	{
    		to[i] = from[i];
    		i++;
    	}
    	to[i] = '\0';
    }
    int main()
    {
    	char a[] = "I am a student.";
    	char b[] = "You are a teacher.";
    	printf("原字符串a为:%s\n", a);
    	printf("原字符串b为:%s\n", b);
    	printf("把字符串a复制到字符串b中\n");
    	copy(a, b);
    	printf("复制后的字符串为:\n%s\n%s\n", a, b);
    	system("pause");
    	return 0;
    }
    

    (2)用字符型指针做为函数实参

    #include<stdio.h>
    #include<stdlib.h>
    void copy(char from[], char to[])		//形参为字符数组
    {
    	int i = 0;
    	while (from[i] != '\0')
    	{
    		to[i] = from[i];
    		i++;
    	}
    	to[i] = '\0';
    }
    int main()
    {
    	char a[] = "I am a student.";
    	char b[] = "You are a teacher.";
    	char *from = a, *to = b;
    	printf("原字符串a为:%s\n", a);
    	printf("原字符串b为:%s\n", b);
    	printf("把字符串a复制到字符串b中\n");
    	copy(from, to);		//实参为字符指针变量
    	printf("复制后的字符串为:\n%s\n%s\n", a, b);
    	system("pause");
    	return 0;
    }
    

    (3)用字符型指针做为函数实参和形参

    #include<stdio.h>
    #include<stdlib.h>
    void copy(char *from, char *to)		//形参为字符指针变量
    {
    	int i = 0;
    	for (from; *from != '\0';from++,to++)
    	{
    		*to = *from;
    	}
    	*to = '\0';
    }
    int main()
    {
    	char *a = "I am a student.";
    	char b[] = "You are a teacher.";
    	char *p=b;
    	printf("原字符串a为:%s\n", a);
    	printf("原字符串b为:%s\n", b);
    	printf("把字符串a复制到字符串b中\n");
    	copy(a,p);		//实参为字符指针变量
    	printf("复制后的字符串为:\n%s\n%s\n", a, b);
    	system("pause");
    	return 0;
    }
    

    此篇博客创作属实不易,博主熬到半夜好几天啦,一字一句地可算整理出来了。如果有错误,请多多指教。

    展开全文
  • 指针式时钟

    2018-01-01 20:47:13
    运行界面是一指针式时钟,包括时分秒三个指针,有12个钟点的显示,并通过指针的转动来实时更新时间。同时可以显示相应时间的数字钟形式。 要求: 通过菜单可以实现闹铃功能、表盘背景、指针颜色设置、倒计时等功能...
  • C++野指针及c++指针使用注意

    千次阅读 多人点赞 2019-08-29 16:11:33
    指针及c++指针使用注意 避免野指针的产生 “野指针”的成因主要有: 1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时...

    内容转自(部分已被本人编辑):https://www.cnblogs.com/mrlsx/p/5419030.html

    野指针及c++指针使用注意点

    避免野指针的产生

    “野指针”的成因主要有:

    1)指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。

    char *p; //此时p为野指针
    

    2)指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针.

    char *p=new char[10];  //指向堆中分配的内存首地址,p存储在栈区
    cin>> p;
    delete []p; //p重新变为野指针
    

    3)指针操作超越了变量的作用范围。          

    char *p=new char[10]; //指向堆中分配的内存首地址
    cin>> p;
    cout<<*(p+10); //可能输出未知数据
    

    指针的注意点:

    a.指针指向常量存储区对象

    char *p="abc";

    此时p指向的是一个字符串常量,不能对*p的内容进行写操作,如srtcpy(p,s)是错误的,因为p的内容为“abc”字符串常量,该数据存储在常量存储区,但可以对指针p进行操作,让其指向其他的内存空间。

    b.资源泄漏

    问题:

    1 #include<iostream>
    2 using namespace std;
    3 void main()
    4 {
    5     char *p=new char[3];  //分配三个字符空间,p指向该内存空间
    6     p="ab";             //此时改变p的指向,p指向了常量“ab”,而不再是new char分配的内存空间了,new char[3]开辟的三个字符空间没有被释放,从而造成了资源泄漏。
    7     delete []p;         //释放时报错,此时p指向的是字符串常量
    8 }

    结果:卡死

     

    改进:

    1 #include<iostream>
    2 using namespace std;
    3 void main()
    4 {
    5     char *p=new char[3];  //分配三个字符空间,p指向该内存空间
    6     strcpy(p,"ab");      //将"ab"存储到p指向的内存空间,而不是改变p的指向
    7     delete []p;         //ok
    8 }

    结果:正确

     

    c.内存越界

    1 char *p=new char[3];  //分配三个字符空间,p指向该内存空间
    2 strcpy(p,"abcd");    //将abcd存处在分配的内存空间中,由于strlen("abcd")=4>3,越界
    3 delete []p;         //ok
    

    d.返回值是指针

    问题:数组p[]中的内容为“hello world”,存储在栈区,函数结束时内容被清除,p变为野指针,可能导致乱码

     1 #include<iostream>
     2 using namespace std;
     3 char *f()
     4 {
     5     char p[]="abc";
     6     return p;
     7 }
     8 void main()
     9 {
    10     cout<<f()<<endl;
    11 }

    结果:

     改进:

    1.加static限定,延长数组生存期

     1 #include<iostream>
     2 using namespace std;
     3 char *f()
     4 {
     5     static char p[]="abc";  //此时数组为静态数组,存储在全局/静态区,生存期到程序结束,因此函数结束时不会销毁p
     6     return p;
     7 }
     8 void main()
     9 {
    10     cout<<f()<<endl;
    11 }

    结果:

     

    2.定义成指针型数组

     1 #include<iostream>
     2 using namespace std;
     3 char *f()
     4 {
     5     char *p="abc";    //"abc"存储在文字常量区,p是指向常量的指针,生存期到程序结束
     6     return p;
     7 }
     8 void main()
     9 {
    10     cout<<f()<<endl;
    11 }

    结果:

     

    3.动态分配存储空间,存储在堆区

     1 #include<iostream>
     2 using namespace std;
     3 char *f()
     4 {
     5     char *p=new char[5];  //动态分配存储空间,p指向堆区
     6     strcpy(p,"abc");   // 这里不能用p="abc",前面已经说明
     7     return p;
     8 }
     9 void main()
    10 {
    11     cout<<f()<<endl;
    12 }

    结果:

     e.指针做形参

    即所谓的地址传递,我们都知道地址传递的方式,形参的改变会导致实参的改变,但要注意的是,这里的改变是指指针所指内容的改变,而不是指针值的改变。因此,当形参改变会导致实参改变时,指针所指的内容是非const类型的,否则会出错。

    1.改变指针内容:

    void swap(int *a,int *b)   //交换的是*a,*b,即指针的内容,而不是指针a,b
    {
       int t;
       t=*a;
       *a=*b;
       *b=t;
    }

    2.改变指针值:

    #include<iostream>
    using namespace std;
    void fun(char *p)
    {
        p="cba";    //“cba”存放在文字常量区,让p指向常量"abc",这里改变的是指针值,实参并不会改变
    }
    void main()
    {
        char *p="abc";  //“abc”存放在文字常量区,p指向常量"abc"
        fun(p);
        cout<<p<<endl;   //输出"abc",而不是"cba"
    }

    结果:

    如果对于这里不容易理解:笔者作如下解释:

    改写代码:(注意看注释)

    #include<iostream>
    using namespace std;
    void fun(char *a)  //给形参换个名字
    {
        cout<<a<<endl;   //abc
        a="cba";         //“cba”存放在文字常量区,让a指向常量"cba"
        cout<<a<<endl;   //cba
    
        //证明这里只改变了指针a的指向,即改变的是指针值a的值,先前是“abc”的地址,后来是“cba”的地址。实参并不会改变。
    }
    void main()
    {
        char *p="abc";  //“abc”存放在文字常量区,p指向常量"abc"
        fun(p);
        cout<<p<<endl;   //输出"abc",而不是"cba"
    
        p="cba";
    	cout<<p<<endl;  //输出“cba”    实参p发生改变
        这就证明:在fun函数中,只是改变了形参的值。
    }

    main函数和fun函数中的"abc"、"cba"都是存放在文字常量区,是具有各自的编号地址的。可以自己调试用printf("%p\n",p);打印出地址,可以看到两个不同的地址。

     

    继续看下面的情况,修改指针的内容:

    #include<iostream>
    using namespace std;
    void fun(char *p)
    {
        p[0]='c';    //改变p的内容,即修改p[0]
    }
    void main()
    {
        char *p="abc";  //p指向常量"abc"
        fun(p);
        cout<<p<<endl;   //error,p所指内容为常量,不能修改
    }

    结果:

    注:p="ab"和strcpy(p,"ab"),含义不一样,前者指针p指向常量“ab”存储区域的首地址,改变了p最开始指向的new申请的内存空间;而后者是将“ab”分配到new申请的内存空间中;

    展开全文
  • 代码如下:Time t1(10,12,15),t2;Time * const ptr1=&t1;ptr1=&t2;定义指向对象的常指针的一般形式为 类名 * const 指针变量=对象地址; 注意应该在定义指针变量时使之初始化 指向对象的常指针变量的值不能被改变...
  •   C语言指针基础知识(一)–指针指针变量   C语言指针基础知识(二)–指针变量的引用   C语言指针基础知识(三)–指针变量作为函数参数   C语言指针基础知识(四)–通过指针引用数组   C语言指针...

    指针系列目录

      C语言指针基础知识点(一)–指针及指针变量
      C语言指针基础知识点(二)–指针变量的引用
      C语言指针基础知识点(三)–指针变量作为函数参数
      C语言指针基础知识点(四)–通过指针引用数组
      C语言指针基础知识点(五)–用数组名作函数参数
      C语言指针基础知识点(六)–通过指针引用多维数组
      C语言指针基础知识点(七)–通过指针引用字符串
      C语言指针基础知识点(八)–返回指针值的函数
      C语言指针基础知识点(九)–指针数组和多重指针
      C语言指针基础知识点(十)–动态内存分配与指向它的指针变量

    通过指针引用多维数组

    1. 多维数组元素的地址

    请记住: *(a+i)a[i] 是等价的

    如果 a 是一维数组名,a[i] 代表 a 数组序号为 i 的元素的存储单元。
    如果 a 是一维数组名,则 a[i] 是一维数组名,它只是一个地址,并不代表某一元素的值。

    二维数组

    一个3行4列的二维数组,他有3行4列,定义为:

    int a[3][4] = {{1,3,5,7},{9,11,13,15},{17,19,21,23}};
    

    在这里插入图片描述
    在这里插入图片描述
    二维数组 a 的有关指针

    表示形式含义地址
    a二维数组名,指向一维数组a[0],即0行首地址2000
    a[0],*(a+0), *a0行0列元素地址2000
    a+1, &a[1]1行首地址2016
    a[1],*(a+1)1行0列元素 a[1][0]的地址2016
    a[1]+2,*(a+1)+2,&a[1][2]1行2列元素 a[1][2]的地址2024
    *(a[1]+2), ((a+1)+2), a[1][2]1行2列元素 a[1][2]的值元素值为13

    再次强调: 二维数组名( 如a )是指向行的。因此a + 1中的 “1’’ 代表一行中全部元素所占的字节数 。 一维数组名 (如a[0],a[1])是指向列元素的。a[0] + 1 中的1代表一个 a 元素所占的字节数。在指向行的指针前面加一个 *,就转换为指向列的指针。 例如,aa+1 是指向行的指针,在它们前面加一个 * 就是 *a*(a+1),它们就成为指向列的指针,分别指向a数组00列的元素和10列的元素。反之,在指向列的指针前面加&,就成为指向行的指针。例如 a[0] 是指向00列元素的指针,在它前面加一个&,得&a[0],由于a[0]*(a+0)等价,因此 &a[0]&*a等价,也就是与a等价,它指向二维数组的 0 行。


    栗子1:

    输出二维数组的地址和值

    # include<stdio.h>
    int main()
    {
    	int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
    	
    	printf("%d,%d\n", a, *a);                 // 0行首地址和0行0列元素地址
    	printf("%d,%d\n", a[0], *(a+0));          // 0行0列元素地址
    
    	printf("%d,%d\n", &a[1][0], *(a+1)+0);    // 1行0列元素地址
    	printf("%d,%d\n", &a[2], *(a+2));         // 2行0列元素地址
    
    	printf("%d,%d\n", a[1][0], *(*(a+1)+0));  // 0行0列元素
    	printf("%d,%d\n", *a[2], *(*(a+2)+0));    // 2行0列元素
    
    	return 0;
    }
    

    在这里插入图片描述
    2. 指向多维数组元素的指针变量

    (1) 指向数组元素的指针变量

    栗子2:

    用指向数组元素的指针变量输出二维数组各元素的值

    # include<stdio.h>
    int main()
    {
    	int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
    	int *p;
    
    	for(p=a[0];p<a[0]+12;p++)   // 使p依次指向下一个元素
    	{
    		if (p !=a[0] && (p-a[0])%4 == 0)
    			printf("\n");     
    		printf("%4d", *p);
    	}
    
    	printf("\n");
    	return 0;
    }
    

    在这里插入图片描述
    (2) 指向由m个元素组成的一维数组的指针变量

    栗子3:

    输出二维数组任一行任一列元素的值

    # include<stdio.h>
    int main()
    {
    	int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
    	int (*p)[4], i ,j;         //指针变量 p 指向包含4个整型元素的一维数组
    
    	p=a;                                      // p指向二维数组的0行           
    	
    	printf("please enter row and column:\n");
    	scanf("%d,%d",&i, &j);                    //输出要求输出的元素的行列号
    
    	printf("a[%d,%d]=%d\n", i,j,*(*(p+i)+j)); // 输出a[i][j]的值
    
    	printf("\n");
    	return 0;
    }
    

    在这里插入图片描述
    (3) 用指向数组的指针作函数参数

    栗子4:

    有一个班,3个学生,各学4门课,计算总平均分数以及第n个学生成绩

    #include <stdio.h>
    
    int main()
    
     {
    	void average(float *p,int n);
        void search(float (*p)[4],int n);
        float score[3][4]={{65,67,70,60},{80,87,90,81},{90,99,100,98}};
        average(*score,12);      //求12个分数的平均分
        
        search(score,2);         //求序号为2的学生的成绩
        return 0;
     }
     
    void average(float *p,int n)
      {
    	float *p_end;
        float sum=0,aver;
        p_end=p+n-1;            // n的值为12时,p_end 的值是 p+11,指向最后一个元素
        for(;p<=p_end;p++)
    		sum=sum+(*p);
        aver=sum/n;
        printf("average=%5.2f\n",aver);
      }
     
    void search(float (*p)[4],int n)
      {
    	int i;
        printf("The score of No.%d are:\n",n);
        for(i=0;i<4;i++)
          printf("%5.2f ",*(*(p+n)+i));
        printf("\n");
      }
    

    在这里插入图片描述
    栗子5:

    查找一门以上课程不及格的学生,输出他们的全部课程的成绩

    #include <stdio.h>
    int main()
    {
    	void search(float(*p)[4],int n);
        float score[3][4]={{65,57,70,60},{58,87,90,81},{90,99,100,98}};
        search(score,3);
        return 0;
    }
    void search(float(*p)[4],int n)
    {
      int i,j,flag;
      for(j=0;j<n;j++)
         {
           flag=0;
           for(i=0;i<4;i++)
             if(*(*(p+j)+i)<60)flag=1;
           
    	   if(flag==1)
             {
    			printf("NO.%d fails,his score are:\n",j+1);
    			for(i=0;i<4;i++)
    				printf("%5.1f",*(*(p+j)+i));
    			printf("\n");
             } 
         }
    }  
    
    

    在这里插入图片描述

    展开全文
  • 指针

    万次阅读 多人点赞 2019-07-28 22:08:42
    一、指针变量的定义和使用 1.定义指针变量 2.通过指针变量取得数据 3.关于*和& 二、指针变量运算(加法、减法、比较运算) 三、数组指针(指向数组的指针) 假设 p 是指向数组 arr 中第 n 个元素的指针,...

    目录

    一、指针变量的定义和使用

           1.定义指针变量

           2.通过指针变量取得数据

           3.关于*和&

    二、指针变量运算(加法、减法、比较运算)

    三、数组指针(指向数组的指针)

           假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?

    四、字符串指针(指向字符串的指针)

           字符数组&&字符串常量

    五、数组灵活多变的访问形式

    六、指针变量作为函数参数

    七、指针作为函数返回值

    八、二级指针(指向指针的指针)

    九、空指针NULL与void指针

           1.空指针NULL

           2.void 指针

    十、数组≠指针

           1.数组在什么时候会转换为指针

           2.关于数组和指针可交换性的总结

    十一、C语言指针数组(每个元素都是指针)

    十二、函数指针(指向函数的指针)

    十三、总结


     

           所谓指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量

           需要注意的是,虽然变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符,但在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名字符串名数组名表示的是代码块或数据块的首地址

    一、指针变量的定义和使用

           数据在内存中的地址也称为指针,如果一个变量存储了一份数据的指针,我们就称它为指针变量

           在C语言中,允许用一个变量来存放指针,这种变量称为指针变量。指针变量的值就是某份数据的地址,这样的一份数据可以是数组、字符串、函数,也可以是另外一个普通变量或者指针变量。

           假设有一个char类型的变量c,它存储了字符'K',并占用了地址为0x11A的内存(地址通常用十六进制表示)。另外有一个指针变量p,它的值为0x11A,正好等于变量c的地址,这种情况我们就称p指向了c,或者说p是指向变量c的指针。

           1.定义指针变量

           定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:

    datatype *name;  或者  datatype *name = value;
    其中,*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型。

           *是一个特殊符号,表明一个变量是指针变量,定义指针变量时必须带*。而给指针变量赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

    int *p1;//p1 是一个指向 int 类型数据的指针变量
    
    int a = 100;
    int *p_a = &a;//在定义指针变量 p_a 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p_a 就指向了 a。值得注意的是,p_a 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。
    
    //定义普通变量
    float a = 99.5, b = 10.6;
    char c = '@', d = '#';
    //定义指针变量
    float *p1 = &a;
    char *p2 = &c;
    //修改指针变量的值
    p1 = &b;
    p2 = &d;
    //*是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带*。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*。
    注:需要强调的是,p1、p2 的类型分别是float*和char*,而不是float和char,它们是完全不同的数据类型,要引起注意。

           指针变量也可以连续定义:

    int *a, *b, *c;  //a、b、c 的类型都是 int*
    注意每个变量前面都要带*。如果写成下面的形式,那么只有 a 是指针变量,b、c 都是类型为 int 的普通变量:
    int *a, b, c;

           2.通过指针变量取得数据

           指针变量存储了数据的地址,通过指针变量能够获得该地址上的数据,格式为:

    *pointer;  其中,*称为指针运算符,用来取得某个地址上的数据,

      *在不同的场景下有不同的作用:*可以用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;使用指针变量时在前面加*表示获取指针指向的数据,或者说表示的是指针指向的数据本身。 

    //通过指针变量取得数据
    #include <stdio.h>
    int main(){
    	int a = 15;
    	int *p = &a;
    	printf("%d,%d\n",a,*p);//两种方式均可输出a的值
    	return 0;
    }
    //运行结果:15,15
    //解析:假设 a 的地址是 0X1000,p 指向 a 后,p 本身的值也会变为 0X1000,*p 表示获取地址 0X1000 上的数据,也即变量 a 的值。从运行结果看,*p 和 a 是等价的。

           我们知道CPU 读写数据必须要知道数据在内存中的地址,普通变量和指针变量都是地址的助记符,虽然通过 *p 和 a 获取到的数据一样,但它们的运行过程稍有不同:a 只需要一次运算就能够取得数据,而 *p 要经过两次运算,多了一层“间接”。
           假设变量 a、p 的地址分别为 0X1000、0XF0A0,它们的指向关系如下图所示:

           程序被编译和链接后,a、p 被替换成相应的地址。使用 *p 的话,要先通过地址 0XF0A0 取得变量 p 本身的值,这个值是变量 a 的地址,然后再通过这个值取得变量 a 的数据,前后共有两次运算;而使用 a 的话,可以通过地址 0X1000 直接取得它的数据,只需要一步运算。也就是说,使用指针是间接获取数据,使用变量名是直接获取数据,前者比后者的代价要高。

    //指针修改内存上的数据
    #include <stdio.h>
    int main(){
    	int a = 15, b = 99, c = 222;
    	int *p = &a;//定义指针变量
    	*p = b;//通过指针变量修改内存上的数据
    	c = *p;//通过指针变量获取内存上的数据
    	printf("%d, %d, %d, %d\n",a,b,c,*p);
    	return 0;
    }
    //运行结果:15,15
    //解析:p指向a,这时*p值为15,*p=b,内存上的值变为99,此时a中的值也变为99。最后c=*p,c中的值也变为99

            定义指针变量时的 * 和使用指针变量时的 * 区别: 

    int *p = &a;//*用在指针变量的定义中,表明这是一个指针变量,以和普通变量区分开;
    *p = 100;//在指针变量前面加*表示获取指针指向的数据
    
    需要注意的是,给指针变量本身赋值时不能加*,如:
    int *p;
    p = &a;//如果写成*p = &a,报错!!!
    *p = 100
    //通过指针交换两个变量的值
    #include <stdio.h>
    int main(){
    	int a = 100, b = 999, temp;
    	int *pa = &a, *pb = &b;
    	printf("a=%d,b=%d\n", a, b);
    	//开始交换
    	temp = *pa;
    	*pa = *pb;
    	*pb = temp;
    	//结束交换
    
    	printf("a=%d,b=%d\n", a, b);
    	return 0;
    }
    //运行结果:
    a=100,b=999
    a=999,b=100

           3.关于*和&

    int a;
    int *p = &a;
    *&a表示什么?*&a可以理解为*(&a),&a表示取变量a的地址(即p),*(&a)表示取这个地址上的数据(即*p),绕来绕去,*&a仍然等于a。
    &*p表示什么?&*p可以理解为&(*p),*p表示取得p指向的数据(即a),&(*p)表示数据的地址(即&a),所以等于p。

           星号*主要有三种用途:

           ①表示乘法,例如int a = 3, b = 5, c;  c = a * b;,这是最容易理解的。

           ②表示定义一个指针变量,以和普通变量区分开,例如int a = 100;  int *p = &a;

           ③表示获取指针指向的数据,是一种间接操作,例如int a, b, *p = &a;  *p = 100;  b = *p;

    二、指针变量运算(加法、减法、比较运算)

           指针变量保存的是地址,而地址本质上是一个整数,所以指针变量可以进行部分运算,例如加法、减法、比较等。但是不能对指针变量进行乘法、除法、取余等其他运算,除了会发生语法错误,也没有实际的含义。

    #include <stdio.h>
    int main(){
    	int a = 10, *pa = &a, *paa = &a;
    	double b = 99.9, *pb = &b;
    	char c = '@', *pc = &c;
    	//最初的值
    	printf("&a=%#X,&b=%#X,&c=%#X\n",&a,&b,&c);
    	printf("pa=%#X,pb=%#X,pc=%#X\n",pa,pb,pc);
    	//加法运算
    	pa++; pb++; pc++;
    	printf("pa=%#X,pb=%#X,pc=%#X\n",pa,pb,pc);
    	//减法运算
    	pa -= 2; pb -= 2; pc -= 2;
    	printf("pa=%#X,pb=%#X,pc=%#X\n",pa,pb,pc);
    	//比较运算
    	if (pa == paa){
    		printf("%d\n", *paa);//比较的是指针变量本身的值,也就是数据的地址。如果地址相等,那么两个指针就指向同一份数据.
    	}
    	else{
    		printf("%d\n",*pa);
    	}
    	return 0;
    }
    //运行结果:
    &a=0X98FB00,&b=0X98FAD8,&c=0X98FAC3
    pa=0X98FB00,pb=0X98FAD8,pc=0X98FAC3
    pa=0X98FB04,pb=0X98FAE0,pc=0X98FAC4
    pa=0X98FAFC,pb=0X98FAD0,pc=0X98FAC2
    -858993460

    三、数组指针(指向数组的指针)

           数组(Array)是一系列具有相同类型的数据的集合,每一份数据叫做一个数组元素(Element)。数组中的所有元素在内存中是连续排列的,整个数组占用的是一块内存。以int arr[] = { 99, 15, 100, 888, 252 };为例,该数组在内存中的分布如下图所示:

           定义数组时,要给出数组名和数组长度,数组名可以认为是一个指针,它指向数组的第 0 个元素。在C语言中,我们将第 0 个元素的地址称为数组的首地址。以上面的数组为例,下图是 arr 的指向:

           数组名的本意是表示整个数组,也就是表示多份数据的集合,但在使用过程中经常会转换为指向数组第 0 个元素的指针,所以上面使用了“认为”一词,表示数组名和数组首地址并不总是等价。初学者可以暂时忽略这个细节,把数组名当做指向第 0 个元素的指针使用即可。

           如果一个指针指向了数组,我们就称它为数组指针(Array Pointer),定义一个指向数组的指针例子如下:

    int arr[] = {23, 56, 4, 34, 45 };
    int *p = arr;
    arr 本身就是一个指针,可以直接赋值给指针变量 p。arr 是数组第 0 个元素的地址,所以int *p = arr;也可以写作int *p = &arr[0];。也就是说,arr、p、&arr[0] 这三种写法都是等价的,它们都指向数组第 0 个元素,或者说指向数组的开头。

           数组指针指向的是数组中的一个具体元素,而不是整个数组,所以数组指针的类型和数组元素的类型有关,上面的例子中,p 指向的数组元素是 int 类型,所以 p 的类型必须也是int *

    //①使用指针的方式遍历数组元素
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int len = sizeof(arr) / sizeof(int);//求数组长度,sizeof(arr) 会获得整个数组所占用的字节数,sizeof(int) 会获得一个数组元素所占用的字节数,它们相除的结果就是数组包含的元素个数,也即数组长度。
    	int i;
    	for (i = 0; i < len; i++){
    		printf("%d ", *(arr + i));//*(arr+i)等价于arr[i],arr 是数组名,指向数组的第 0 个元素,表示数组首地址, arr+i 指向数组的第 i 个元素,*(arr+i) 表示取第 i 个元素的数据,它等价于 arr[i]。
    	}
    	printf("\n");
    	return 0;
    }
    //运行结果:23 56 4 34 45
    
    //②使用数组指针来遍历数组元素
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int i, *p = arr, len = sizeof(arr) / sizeof(int);
        //求数组的长度时不能使用sizeof(p) / sizeof(int),因为 p 只是一个指向 int 类型的指针,编译器并不知道它指向的到底是一个整数还是一系列整数(数组),所以 sizeof(p) 求得的是 p 这个指针变量本身所占用的字节数,而不是整个数组占用的字节数。
    
    	for (i = 0; i < len; i++){
    		printf("%d ", *(p + i));
    	}
    	printf("\n");
    	return 0;
    }
    //运行结果:23 56 4 34 45
    
    //③让 p 指向数组中的第三个元素
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int *p = &arr[2];//也可以写做int *p = arr +2,这里指向第三个元素
    	printf("%d,%d,%d\n",*(p-2),*p,*(p+2));
    	return 0;
    }
    //运行结果:23,4,45

           引入数组指针后,我们就有两种方案来访问数组元素了,一种是使用下标,另外一种是使用指针。

           (1) 使用下标

           也就是采用 arr[i] 的形式访问数组元素。如果 p 是指向数组 arr 的指针,那么也可以使用 p[i] 来访问数组元素,它等价于 arr[i]。

           (2) 使用指针

           也就是使用 *(p+i) 的形式访问数组元素。另外数组名本身也是指针,也可以使用 *(arr+i) 来访问数组元素,它等价于 *(p+i)。

           不管是数组名还是数组指针,都可以使用上面的两种方式来访问数组元素。不同的是,数组名是常量,它的值不能改变,而数组指针是变量(除非特别指明它是常量),它的值可以任意改变。也就是说,数组名只能指向数组的开头,而数组指针可以先指向数组开头,再指向其他元素

           假设 p 是指向数组 arr 中第 n 个元素的指针,那么 *p++、*++p、(*p)++ 分别是什么意思呢?

    //*p++
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int i, *p = arr, len = sizeof(arr) / sizeof(int);
    
    	for (i = 0; i < len; i++){
    		printf("%d ", *p++);
    	}
    	printf("\n");
    	return 0;
    }
    //运行结果:23 56 4 34 45
    //解析:*p++ 应该理解为 *(p++),每次循环都会改变 p 的值(p++ 使得 p 自身的值增加),以使 p 指向下一个数组元素。该语句不能写为 *arr++,因为 arr 是常量,而 arr++ 会改变它的值,这显然是错误的。
    
    //*++p
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int i, *p = arr, len = sizeof(arr) / sizeof(int);
    
    	for (i = 0; i < len; i++){
    		printf("%d ", *++p);
    	}
    	printf("\n");
    	return 0;
    }
    //运行结果:56 4 34 45 -858993460
    //解析:*++p 等价于 *(++p),会先进行 ++p 运算,使得 p 的值增加,指向下一个元素,整体上相当于 *(p+1),所以会获得第 n+1 个数组元素的值。最后一个值为随机值。
    
    //(*p)++
    #include <stdio.h>
    int main(){
    	int arr[] = { 23, 56, 4, 34, 45 };
    	int i, *p = arr, len = sizeof(arr) / sizeof(int);
    
    	for (i = 0; i < len; i++){
    		printf("%d ", (*p)++);
    	}
    	printf("\n");
    	return 0;
    }
    //运行结果:23 24 25 26 27
    //解析:(*p)++ 就非常简单了,会先取得第1个元素的值,再对该元素的值加 1。这里第1个元素的值为 23,执行完该循环语句后,第 1个元素的值依次会变为 24 25 26 27。

    四、字符串指针(指向字符串的指针)

           C语言没有字符串类型,通常是将字符串放在一个字符数组中。使用指针的方式输出字符串:

    #include <stdio.h>
    #include <string.h>
    int main(){
    	char str[] = "www.baidu.com";
    	char *pstr = str;
    	int len = strlen(str), i;
    
    	//使用*(pstr+i)
    	for (i = 0; i < len; i++){
    		printf("%c", *(pstr + i));
    	}
    	printf("\n");
    
    	//使用pstr[i]
    	for (i = 0; i < len; i++){
    		printf("%c", pstr[i]);
    	}
    	printf("\n");
    
    	//使用*(str+i)
    	for (i = 0; i < len; i++){
    		printf("%c",*(str+i));
    	}
    	printf("\n");
    
    	return 0;
    }
    //运行结果:
    www.baidu.com
    www.baidu.com
    www.baidu.com

           除了字符数组,C语言还支持另外一种表示字符串的方法,就是直接使用一个指针指向字符串,例如:

    char *str = "www.baidu.com";
    或者
    char *str;
    str = "www.baidu.com";
    //字符串中的所有字符在内存中是连续排列的,str 指向的是字符串的第 0 个字符;我们通常将第 0  个字符的地址称为字符串的首地址。字符串中每个字符的类型都是char,所以 str 的类型也必须是char *。
    //输出字符串:使用%s输出整个字符串,使用*或[ ]获取单个字符
    #include <stdio.h>
    #include <string.h>
    int main(){
    	char *str = "www.baidu.com";
    	int len = strlen(str), i;
    	//直接输出字符串
    	printf("%s\n", str);
    
    	//使用*(str+i)
    	for (i = 0; i < len; i++){
    		printf("%c",*(str+i));
    	}
    	printf("\n");
    
    	//使用str[i]
    	for (i = 0; i < len; i++){
    		printf("%c", str[i]);
    	}
    	printf("\n");
    
    	return 0;
    }
    //运行结果:
    www.baidu.com
    www.baidu.com
    www.baidu.com

           字符数组&&字符串常量

           在编程中如果只涉及到对字符串的读取,那么字符数组字符串常量都能满足需求;如果有写入(修改)操作,那么只能使用字符数组,不能使用字符串常量。

           获取用户输入的字符串就是一个典型的写入操作,只能使用字符数组,不能使用字符串常量,请看下面的代码:

    #include <stdio.h>
    int main(){
    	char str[30];
    	gets(str);
    	printf("%s\n", str);
    
    	return 0;
    }
    //运行结果:
    www.baidu.com
    www.baidu.com

           总结:C语言有两种表示字符串的方法,一种是字符数组,另一种是字符串常量,它们在内存中的存储位置不同,使得字符数组可以读取修改,而字符串常量只能读取不能修改。

    五、数组灵活多变的访问形式

    #include <stdio.h>
    int main(){
    	char str[20] = "www.baidu.com";
    	char *s1 = str;
    	char *s2 = str + 4;
    	char c1 = str[4];
    	char c2 = *str;
    	char c3 = *(str + 4);
    	char c4 = *str + 2;
    	char c5 = (str + 1)[5];
    
    	int num1 = *str + 2;
    	long num2 = (long)str;
    	long num3 = (long)(str + 2);
    
    	printf("s1=%s\n", s1);
    	printf("s2=%s\n", s2);
    	printf("c1=%c\n", c1);
    	printf("c2=%c\n", c2);
    	printf("c3=%c\n", c3);
    	printf("c4=%c\n", c4);
    	printf("c5=%c\n", c5);
    	printf("num1=%d\n", num1);
    	printf("num2=%ld\n", num2);
    	printf("num3=%ld\n", num3);
    
    	return 0;
    }
    //运行结果:
    s1=www.baidu.com
    s2=baidu.com
    c1=b
    c2=w
    c3=b
    c4=y
    c5=i
    num1=121
    num2=17824436
    num3=17824438
    //解析:
    (1) str既是数组名称,也是一个指向字符的指针;指针可以参加运算,指针加1相当于数组下标加1。
        printf() 输出字符串时,要求给定一个起始地址,并从这个地址开始输出,直到遇见NUL(\0)停止。s1 为字符串str第1个字符的地址,s2 为第4个字符的地址,所以 printf() 的结果分别为 www.baidu.com 和 baidu.com。
    (2) 数组元素的访问形式可以看做 address[offset],address 为起始地址,offset 为偏移量,所以:
    c1 = str[4] 表示以地址 str 为起点,向后偏移4个字符,为b;
        str表示第0个字符的地址,*str表示第0个字符,即c2=w;因为指针可以参加运算,所以 str+4 表示第4个字符的地址,c3 = *(str+4) 表示第4个字符,即b。
        字符与整数运算时,先转换为整数(字符对应的ASCII码)。对于c4,*str为字符w(同c2),w对应的ASCII为119,所以c4=119+2=121,转化为字符对应为y。
        c5 = (str+1)[5] 表示以地址 str+1 为起点,向后偏移5个字符,等价于str[6],为i。
    (3) 字符与整数运算时,先转换为整数(字符对应的ASCII码)。对于 num1,*str+2 == w+2 == 119+2 == 121,即num1为121;num2和num3分别为字符串str的首地址和第2个元素的地址。
    

    六、指针变量作为函数参数

           在C语言中,函数的参数不仅可以是整数、小数、字符等具体的数据,还可以是指向它们的指针。用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。

    //使用指针变量作参数交换两个变量的值
    #include <stdio.h>
    void swap(int *p1, int *p2){
    	int temp;//临时变量
    	temp = *p1;
    	*p1 = *p2;
    	*p2 = temp;
    }
    
    int main(){
    	int a = 10, b = 20;
    	swap(&a, &b);
    	printf("a=%d,b=%d\n",a,b);
    	return 0;
    }
    //运行效果:a=20,b=10
    //解析:调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 *p1、*p2 代表的就是变量 a、b 本身,交换 *p1、*p2 的值也就是交换 a、b 的值。函数运行结束后虽然会将 p1、p2 销毁,但它对外部 a、b 造成的影响是“持久化”的,不会随着函数的结束而“恢复原样”。

    七、指针作为函数返回值

           C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。

    //定义函数 strlong(),用来返回两个字符串中较长的一个
    #include <stdio.h>
    #include <string.h>
    
    char *strlong(char *str1, char *str2){
    	if (strlen(str1) >= strlen(str2)){
    		return str1;
    	}
    	else{
    		return str2;
    	}
    }
    
    int main(){
    	char str1[30], str2[30], *str;
    	gets(str1);
    	gets(str2);
    	str = strlong(str1, str2);
    	printf("Longer string:%s\n", str);
    
    	return 0;
    }
    //运行结果:
    www.baidu.com
    www.ali.com
    Longer string:www.baidu.com

           用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。

    八、二级指针(指向指针的指针)

           指针可以指向一份普通类型的数据,例如 int、double、char 等,也可以指向一份指针类型的数据,例如 int *、double *、char * 等。如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针
          假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:

    C语è¨äºçº§æéï¼æåæéçæéï¼æ¼ç¤ºå¾

           指针变量也是一种变量,也会占用存储空间,也可以使用 &获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号 *。p1 是一级指针,指向普通类型的数据,定义时有一个 *;p2 是二级指针,指向一级指针 p1,定义时有两个*。将这种关系转换为C语言代码:

    int a =100;
    int *p1 = &a;
    int **p2 = &p1;
    //如果我们希望再定义一个三级指针 p3,让它指向 p2,那么可以这样写:
    int ***p3 = &p2;
    //四级指针也是类似的道理:
    int ****p4 = &p3;

           想要获取指针指向的数据时,一级指针加一个*,二级指针加两个*,三级指针加三个*,以此类推,请看代码:

    #include <stdio.h>
    int main(){
    	int a = 100;
    	int *p1 = &a;
    	int **p2 = &p1;
    	int ***p3 = &p2;
    
    	printf("%d,%d,%d,%d\n",a,*p1,**p2,***p3);
    	printf("&p2=%#X,p3=%#X\n", &p2, p3);
    	printf("&p1=%#X,p2=%#X,*p3=%#X\n", &p1, p2, *p3);
    	printf("&a=%#X,p1=%#X,*p2=%#X,**p3=%#X\n", &a, p1, *p2, **p3);
    	return 0;
    }
    //运行结果:
    100,100,100,100
    &p2=0X75FB70,p3=0X75FB70
    &p1=0X75FB7C,p2=0X75FB7C,*p3=0X75FB7C
    &a=0X75FB88,p1=0X75FB88,*p2=0X75FB88,**p3=0X75FB88
    //解析:以三级指针 p3 为例来分析上面的代码。***p3等价于*(*(*p3))。*p3 得到的是 p2 的值,也即 p1 的地址;*(*p3) 得到的是 p1 的值,也即 a 的地址;经过三次“取值”操作后,*(*(*p3)) 得到的才是 a 的值。
    

           假设 a、p1、p2、p3 的地址分别是 0X00A0、0X1000、0X2000、0X3000,它们之间的关系可以用下图来描述(方框里面是变量本身的值,方框下面是变量的地址。):

    C语è¨å¤çº§æéæ¼ç¤ºå¾

    九、空指针NULL与void指针

           1.空指针NULL

           一个指针变量可以指向计算机中的任何一块内存,不管该内存有没有被分配,也不管该内存有没有使用权限,只要把地址给它,它就可以指向,C 语言没有一种机制来保证指向的内存的正确性,程序员必须自己提高警惕。

           很多初学者会在无意间对没有初始化的指针进行操作,这是非常危险的,请看下面的例子:

    #include <stdio.h>
    int main(){
    	char *str;
    	gets(str);
    	printf("%s\n", str);
    	return 0;
    }

           这段程序没有语法错误,但在Windows下编译连接会有警告,没有对指针进行初始化!我们知道,未初始化的局部变量的值是不确定的,C 语言并没有对此作出规定,不同的编译器有不同的实现,大家不要直接使用未初始化的局部变量。上面的代码中, str  就是一个未初始化的局部变量,它的值是不确定的,究竟指向哪块内存也是未知的,大多数情况下这块内存没有被分配或者没有读写权限,使用  gets()  函数向它里面写入数据显然是错误的。

           C语言中可以对没有初始化的指针赋值为 NULL ,例如:char *str = NULL;NULL  是 “ 零值、等于零 ” 的意思,在 C 语言中表示空指针。从表面上理解,空指针是不指向任何数据的指针,是无效指针,程序使用它不会产生效果。注意区分大小写,null  没有任何特殊含义,只是一个普通的标识符。

    //给str赋值NULL
    #include <stdio.h>
    int main(){
    	char *str = NULL;
    	gets(str);
    	printf("%s\n", str);
    	return 0;
    }
    //貌似也会报错

           注意,C 语言没有规定 NULL 的指向,只是大部分标准库约定成俗地将 NULL 指向  0 ,所以不要将 NULL 和 0 等同起来,例如这种写法是不专业的:int *p = 0; 而应该坚持写为:int *p = NULL;

           注意 NULL 和 NUL 的区别:NULL 表示空指针,是一个宏定义,可以在代码中直接使用。而 NUL 表示字符串的结束标志  ’\0’ ,它是 ASCII 码表中的第  0  个字符。 NUL 没有在C语言中定义,仅仅是对  ’\0’ 的称呼,不能在代码中直接使用。

           2.void 指针

           void 用在函数定义中可以表示函数没有返回值或者没有形式参数,用在这里表示指针指向的数据的类型是未知的。也就是说,  void *  表示一个有效指针,它确实指向实实在在的数据,只是数据的类型尚未确定,在后续使用过程中一般要进行强制类型转换。C 语言动态内存分配函数  malloc()  的返回值就是  void * 类型,在使用时要进行强制类型转换。

    #include <stdio.h>
    #include <malloc.h>
    int main(){
    	//分配可以保存30个字符的内存,并把返回的指针转换为char *
    	char *str = (char *)malloc(sizeof(char) * 30);
    	gets(str);
    	printf("%s\n",str);
    
    	return 0;
    }
    //运行结果:
    www.baidu.com
    www.baidu.com

           注意:void * ,它不是空指针的意思,而是实实在在的指针,只是指针指向的内存中不知道保存的是什么类型的数据。

    十、数组≠指针

           数组和指针不等价,数组是另外一种类型。数组和指针不等价的一个典型案例就是求数组的长度,这个时候只能使用数组名,不能使用数组指针:

    #include <stdio.h>
    int main(){
    	int a[6] = { 0, 1, 2, 3, 4, 5 };
    	int *p = a;
    	int len_a = sizeof(a) / sizeof(int);
    	int len_p = sizeof(p) / sizeof(int);
    	printf("len_a=%d,len_p=%d\n",len_a,len_p);
    
    	return 0;
    }
    //运行结果:len_a=6,len_p=1

           数组是一系列数据的集合,没有开始和结束标志,p 仅仅是一个指向 int 类型的指针,编译器不知道它指向的是一个整数还是一堆整数,对 p 使用 sizeof 求得的是指针变量本身的长度。也就是说,编译器并没有把 p 和数组关联起来,p 仅仅是一个指针变量,不管它指向哪里,sizeof 求得的永远是它本身所占用的字节数。

           1.数组在什么时候会转换为指针

           数组名的本意是表示一组数据的集合,它和普通变量一样,都用来指代一块内存,但在使用过程中,数组名有时候会转换为指向数据集合的指针(地址),而不是表示数据集合本身,这在前面的例子中已经被多次证实。

           数据集合包含了多份数据,直接使用一个集合没有明确的含义,将数组名转换为指向数组的指针后,可以很容易地访问其中的任何一份数据,使用时的语义更加明确。

           C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof 或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。

           2.关于数组和指针可交换性的总结

           (1) 用 a[i] 这样的形式对数组进行访问总是会被编译器改写成(或者说解释为)像 *(a+i) 这样的指针形式。

           (2) 指针始终是指针,它绝不可以改写成数组。你可以用下标形式访问指针,一般都是指针作为函数参数时,而且你知道实际传递给函数的是一个数组。

           (3) 在特定的环境中,也就是数组作为函数形参,也只有这种情况,一个数组可以看做是一个指针。作为函数形参的数组始终会被编译器修改成指向数组第一个元素的指针。

           (4) 当希望向函数传递数组时,可以把函数参数定义为数组形式(可以指定长度也可以不指定长度),也可以定义为指针。不管哪种形式,在函数内部都要作为指针变量对待。

    十一、C语言指针数组(每个元素都是指针)

           注意区别: 
           数组指针:指的是指针指向的是一个数组; 
           指针数组:表示的是数组的每个元素都是指针。

           如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为: 

    dataType *arrayName[length]; 其中,[ ]的优先级高于*,该定义形式应该理解为: 
    dataType *(arrayName[length]); 括号里面说明arrayName是一个数组,包含了length个元素,括号外面说明每个元素的类型为dataType *。

           除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,下面是一个简单的例子:

    include <stdio.h>
    int main(){
        int a = 16, b = 932, c = 100;
        //定义一个指针数组
        int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *parr[]
        //定义一个指向指针数组的指针
        int **parr = arr;
        printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
        printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
    
        return 0;
    }
    //运行结果:
    16, 932, 100
    16, 932, 100
    //解析:
    1. arr 是一个指针数组,它包含了 3 个元素,每个元素都是一个指针,在定义 arr 的同时,我们使用变量 a、b、c 的地址对它进行了初始化,这和普通数组是多么地类似。
    2. parr 是指向数组 arr 的指针,确切地说是指向 arr 第 0 个元素的指针,它的定义形式应该理解为int (*parr),括号中的表示 parr 是一个指针,括号外面的int 表示 parr 指向的数据的类型。arr 第 0 个元素的类型为 int ,所以在定义 parr 时要加两个 *。
    3. 第一个 printf() 语句中,arr[i] 表示获取第 i 个元素的值,该元素是一个指针,还需要在前面增加一个 * 才能取得它指向的数据,也即 *arr[i] 的形式。
    4. 第二个 printf() 语句中,parr+i 表示第 i 个元素的地址,(parr+i) 表示获取第 i 个元素的值(该元素是一个指针),*(parr+i) 表示获取第 i 个元素指向的数据。
    

           指针数组还可以和字符串数组结合使用,请看下面的例子:

    #include <stdio.h>
    int main(){
        char *str[3] = {
            "google.com",
            "hello world",
            "C Language"
        };
        printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
        return 0;
    }
    //运行结果:
    google.com
    hello world
    C Language
    //需要注意的是,字符数组 str 中存放的是字符串的首地址,不是字符串本身,字符串本身位于其他的内存区域,和字符数组是分开的。也只有当指针数组中每个元素的类型都是char *时,才能像上面那样给指针数组赋值,其他类型不行。
    //为了便于理解,可以将上面的字符串数组改成下面的形式,它们都是等价的。
    #include <stdio.h>
    int main(){
    	char *str0 = "google.com";
    	char *str1 = "hello world";
    	char *str2 = "C Language";
    	char *str[3] = { str0, str1, str2 };
    	printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
    	return 0;
    }
    //运行结果:
    google.com
    hello world
    C Language

           指针数组和二维数组指针的区别

           指针数组和二维数组指针在定义时非常相似,只是括号的位置不同:

           int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];

           int (*p2)[5]; //二维数组指针,不能去掉括号

    十二、函数指针(指向函数的指针)

           一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址,这和数组名非常类似。我们可以把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针

           函数指针的定义形式为:

    returnType (*pointerName)(param list);
    其中,returnType 为函数返回值类型,pointerNmae 为指针名称,param list 为函数参数列表。参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。
    注意( )的优先级高于*,第一个括号不能省略,如果写作returnType *pointerName(param list);就成了函数原型,它表明函数的返回值类型为returnType *。
    //用指针来实现对函数的调用
    #include <stdio.h>
    //返回两个数中较大的一个
    int max(int a, int b){
        return a>b ? a : b;
    }
    int main(){
        int x, y, maxval;
        //定义函数指针
        int (*pmax)(int, int) = max;  //也可以写作int (*pmax)(int a, int b)
        printf("Input two numbers:");
        scanf("%d %d", &x, &y);
        maxval = (*pmax)(x, y);
        printf("Max value: %d\n", maxval);
        return 0;
    }
    //运行结果:
    Input two numbers:10 30
    Max value: 30
    //解析:第 13 行代码对函数进行了调用。pmax 是一个函数指针,在前面加 * 就表示对它指向的函数进行调用。注意( )的优先级高于*,第一个括号不能省略。
    

    十三、总结

           指针(Pointer)就是内存的地址,C语言允许用一个变量来存放指针,这种变量称为指针变量。指针变量可以存放基本类型数据的地址,也可以存放数组、函数以及其他指针变量的地址。

           程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。

           (1) 指针变量可以进行加减运算,例如p++p+ip-=i。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。
           (2) 给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
           (3) 使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值NULL
           (4) 两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。
           (5) 数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof、& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。

     

     

     

    参考文献:C语言中文网

    展开全文
  • 函数指针(指向函数的指针) 与数据项类似,函数也有自己的地址。函数的地址是存储其机器代码的内存的开始地址。例如,可以编写将另一个函数的地址作为参数的函数,这样第一个函数将能够找到第二个函数,并运行它。...
  • 深入理解指针以及二级指针指针指针

    千次阅读 多人点赞 2017-08-11 12:05:43
    前言:本文将讲解指针的定义、指针变量和普通变量的本质区别、一级指针和二级指针的关系以及如何通过二级指针修改一级指针所指向的内存。文末还附加了两个实例,帮助读者加深对二级指针的理解。本文试图通过图表的...
  • 《C和指针》,人民邮电出版社出版,外文书名:...第12章 使用结构和指针 第13章 高级指针话题 第14章 预处理器 第15章 输入/输出函数 第16章 标准函数库 第17章 经典抽象数据类型 第18章 运行时环境 附录 部分问题答案
  • C++指针详解

    万次阅读 多人点赞 2018-12-08 22:55:09
    有好的一面,必然会有坏的一面,指针的灵活导致了它的难以控制,所以C/C++程序员的很多bug是基于指针问题上的。今天就对指针进行详细的整理。 1、指针是什么? 指针是“指向(point to)”另外一种类型的复合类型...
  • 征服指针(C)

    2018-01-20 13:00:44
    1.1.6 C的理念 12 1.1.7 C的主体 14 1.1.8 C是只能使用标量的语言 15 1.2 关于指针 16 1.2.1 恶名昭著的指针究竟是什么 16 1.2.2 和指针的第一次亲密接触 17 1.2.3 指针和地址之间的微妙关系 23 1.2.4 指针...
  • 是指向一个结构体的指针,也可以当做一个结构体数组来用。 可以把结构体当做 int 来看,int* 类型即可以指向一个整数,也可以指向数组。 struct T ** ptr, 说白了 就是一个结构体指针的数组,数组内都是指针,还要...
  • c和指针.pdf

    热门讨论 2014-07-02 09:42:31
    12章 使用结构和指针 12.1 链表 12.2 单链表 12.2.1 在单链表中插入 12.2.2 其他链表操作 12.3 双链表 12.3.1 在双链表中插入 12.3.2 其他链表操作 12.4 总结 第13章 高级指针话题 13.1 进一步探讨指向...
  • 指向具有M个元素的一维数组指针 ...int a[3][4] = {{1,2,3,4}, {5,6,7,8}, {9,10,11,12}}; int (*p)[4]; for(p=a; p<a+3; p++) // p++ 行++ { //输出p所指行的各列元素值 for(int j=0; j<
  • c语言 指针集合

    千次阅读 多人点赞 2021-03-04 19:21:24
    指针集合 指针概念 指针类型集合 字符型指针 整形指针 浮点数指针 指针步长 数组指针 函数指针 数组指针概念 数组指针指针的关系 一般函数指针 函数指针数组 指针概念 什么是指针 在计算机科学中,指针(Pointer)...
  • Time t1(10,12,15),t2; //定义对象 Time * const ptr1; //const位置在指针变量名前面,规定ptr1的值是常值 ptr1=&t1; //ptr1指向对象t1,此后不能再改变指向 ptr1=&t2; //错误,ptr1不能改变指向 定义指向对象的...
  • c语言指针用法详解,通俗易懂超详细!

    千次阅读 多人点赞 2021-07-18 00:10:30
    文章转自:无际单片机大家好,我是无际。今天给大家来讲解一下指针。我会由浅到深,最后结合实际应用讲解,让大家学会指针的同时,知道大佬们都用指针来干嘛!长文预警!全文大约5200多字,学指针看...
  • C++ 裸指针和智能指针

    千次阅读 2019-12-07 12:11:54
    C++ 裸指针和智能指针指针的缺陷及实例演示忘记释放指针指向的资源代码逻辑过早结束 导致释放资源的代码执行不到代码运行过程中,代码发生异常了,直接出当前函数栈帧了通过裸指针,访问对象时,无法判断对象是否...
  • 指针的艺术》主要探讨C语言指针的基本概念及其与变量、数组、结构体和文件之间的应用,同时剖析C++、C++/CLR、Java、Visual C#及Visual Basic中的引用(Reference),并分别以指针和引用(Reference)实现链表、堆栈与...
  • C++对象和指针的引用

    2012-04-26 23:34:36
    因为使用对象指针作函数参数有如下两好处: (1) 实现传址调用。可在被调用函数中改变调用函数的参数对象的值,实现函数之间的信息传递。 (2) 使用对象指针实参仅将对象的地址值传给形参,而不进行副本的拷贝,...
  • C语言指针全归纳-进阶版

    千次阅读 多人点赞 2021-03-24 08:50:42
    大家好,上次的初阶版让我第一次尝到了写博客的甜头,原来可以有这么多人看我的博客呀,非常感谢大家对我的支持与鼓励,我一定会再接再厉,继续创作出优质的内容来向大家分享知识。...指针具有类型
  • 1.指针数组 即指针的数组,本质是一个数组,数组的每个元素都是指针变量   定义指针数组,含有4个元素,每个元素都是一个char *类型的指针,指向字符串 char *p[] = { "lemon","C language&...
  • 指针也可以指向一个结构体,定义的...} stu1 = { Tom, 12, 18, 'A', 136.5 }; //结构体指针struct stu *pstu = &stu1; 也可以在定义结构体的同时定义结构体指针: struct stu{ char *name; //姓名 int num; //学
  • 指针指针(简单易懂)

    千次阅读 多人点赞 2019-09-29 15:30:08
    int a = 12; int *b = &a; 内存的分配如下 这时再来一个变量 c = &b; 问题来了? c 是什么类型? b 是指向整型的指针 ,c 是指向整形指针指针? 是的 c 是指向指针指针 声明如下 int ** c; int a = 12; ...
  • 让你不再害怕指针——C指针详解(经典,非常详细)

    万次阅读 多人点赞 2016-04-12 10:53:34
     要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,...
  • 先来介绍一下指针:  指针一种类型,理论上来说它包含其他变量的地址,因此有的书上也叫它:地址变量。 是类型就有大小且都是4字节大小,里边只是存储了一个变量的地址而已。 大小为4字节是因为寻址空间为(32位)0x...
  • C语言中的指针

    2021-05-21 03:18:07
    -------------------------------指针---------------------------------指针?内存单元的编号也叫做地址。既然根据内存单元的 号或地址就可以找到所需的内存单元,所以通常也把这个地址称为指针。---总结:对于一个...
  • c++数组指针指针数组详解

    万次阅读 多人点赞 2019-03-14 15:33:16
    指针数组可以说成是”指针的数组”,首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。 数组指针: 数组指针可以说成是”数组的指针”...
  • 东北大学OJ-1475: 实验12-4:指针实现字符串连接(指针下)   大家好,我叫亓官劼(qí guān jié ),在CSDN中记录学习的点滴历程,时光荏苒,未来可期,加油~博客地址为:亓官劼的博客,B站昵称为:亓官劼,...
  • C语言指针详解(经典,非常详细)

    万次阅读 多人点赞 2019-06-01 17:26:12
    要了解指针,多多少少会出现一些比较复杂的类型,所以我先介绍一下如何完全理解一个复杂类型,要理解复杂类型其实很简单,一个类型里会出现很多运算符,他们也像普通的表达式一样,有优先级,其优先级和运算优先级一样,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 561,734
精华内容 224,693
关键字:

十二点的指针