C语言:指针的学习 ---翁恺C语言笔记

 &

定义

  •  & 用于获得变量的地址,操作符必须是变量
  •  & 不能对没有地址的东西取地址

对“&”的读取

在64位架构下,int 与地址的 size 不同,因此不要用 %x ,要用 %p ,防止出错

( " %#p " 可以强制显示 0x  )

对数组读取地址

  • &a == a ==a [ 0 ] , 且 a [ 0 ] 和 a [ 1 ] 之间相差数组类型个字节 ( 如 int 型相差 4 ) 

地址的排布

以 “ 堆栈 ” 排布,自上而下的分布 

指针定义

指针 

  • 保存地址的变量

int *p = & i ;  -------------->  注意加 “ & ”

int *p , q ;     -------------->  表示 p 是指针变量,q 只是普通的 int 变量

int *p , *q ;    ------------->    表示 p , q 都是指针变量

指针变量

  • 变量的值是内存的地址

  • 一般有指针,就初始化它为 0 ; 

作为参数的指针

void f( int *p );
  • 在被调用的时候得到了某个变量的地址
int i = 0 ;
f ( &i ) ;    //注意 “ & ”
  • 在函数里可以通过这个指针访问外面的变量  i 

用 %p 来输出地址

可通过下面程序简单了解指针与格式:

#include <stdio.h>
void f( int *p ) ;

int main()
{
	int i = 6 ;
	printf ("&i=%p\n" , &i ) ;  //注意“ & ”
	f( &i ) ;
	return 0;
}
void f( int *p )  //此处通过指针访问 main 函数中 i 的值
{
	printf (" p=%p\n" , p ) ;   // p 存放 i 的地址
}

 访问地址上的变量:*

  • * 是单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做左值,也可以做右值
	int a = 5 ;
	int *p = &a ; //这当中int *p 定义了一个指向 int 类型的指针变量 p,所以可以用 &a
	
	printf (" p=%p\n" , p ) ;  //p存放的是a的地址 
	printf ("*p=%d\n" , *p ) ;  //*p存放的是a的值,也是一个int类型的变量
	
	*p = 26 ;
	printf ("a=%d\n" , a ) ; 
  • 当传入函数的变量是一个地址,那么在函数内对这个变量进行改变,可以影响到函数外的内容 

指针应用

函数返回多个值

在学指针之前,你或许试过用函数交换两个数,这两个数在函数内部交换成功,但在函数外就没用了

//这是错误的!脱离函数就不管用了
void swap(int x,int y){
		int t=x;
		x=y;
		y=t;
	}	

现在,我们用指针来进行交换:

#include <stdio.h>
void swap ( int *pa , int *pb ) ;
int main()
{
	int a = 2 , b = 3 ;
	swap ( &a , &b ) ; 
	printf ("a=%d,b=%d" , a , b ) ;
	return 0;
}
void swap ( int *pa , int *pb )
{
	int t = *pa ;
	*pa = *pb ;
	*pb = t ;	
}

函数返回多个值时,有些值只能通过指针返回

传入的参数   --------->  需要保存所带回的结果的变量

函数返回运算的状态

  • 函数返回运算的状态,结果通过指针返回

(即用返回值判断 true / false( 常让函数返回特殊的不属于有效范围的值来表示出错,如0,-1)

  • 当任何数值都是有效的可能结果时,就得分开返回

                一般函数状态用 return 返回,实际值用指针参数返回

如下述简单除法:

#include <stdio.h>
int divide (int a , int b , int *result ) ;
int main()
{
	int a , b , c ;
	scanf ("%d %d" , &a , &b ) ;
	if ( divide(a,b,&c) ){
		printf ("%d/%d=%d\n" , a , b , c ) ;
	}	
	return 0;
}
int divide(int a , int b , int *result )
{
	int ret = 1 ;
	if ( b==0 ){
		ret = 0 ;
	}else{
		*result = a/b ;   //返回 *result 的值
	}
	return ret ;  //返回判断结果
}

注意误区

指针必须先定义并指向变量,才能使用

即不可以直接 

int *p ;
*p = 12 ;

此时的 p 可能指向任何地方,则 *p 的赋值是不安全的 

指针与数组 

函数参数表中的数组,必须留空的方括号,且不能用 sizeof () 得到正确元素个数 

void max ( int a[] )
  • 函数参数表里的数组实际上是指针

sizeof ( a ) == sizeof ( int *)    // a 为数组名,仅表示作为函数原型是等价的

以下两行代码表示意义一样 

int *ar ;
int ar [] ;

数组变量是特殊的指针

  • 数组变量本身表达地址,无需用 & 取地址;
  • 数组的单元表达的是变量,需用 & 取地址;
  • a == &a[ 0 ]

*  与 [ ]

  •  [ ] 可以对数组做,也可以对指针做

                 *p = p [ 0 ]

  • * 可以对数组做,也可以对指针做

                a [ 0 ] = * a

  • 数组变量是const的指针,所以不能被赋值

                int a[ ]  <----> int * const a

指针与数组

以下是一些喜欢使用指针来表达数组的原因:

  1. 内存效率:使用指针可以节省内存空间。当需要传递一个数组作为函数的参数时,如果直接传递数组名,会将整个数组拷贝一份到函数的栈帧中,而使用指针传递数组只需要传递数组的地址,减少了内存的开销。
  2. 灵活性:使用指针可以更灵活地访问和操作数组元素。指针可以进行算术运算,可以通过指针进行指针偏移来访问不同位置的数组元素,这样能够方便地实现数组的遍历、查找和修改等操作。
  3. 动态内存分配:指针与动态内存分配紧密相关。通过使用指针和相关的内存管理函数(如malloc和free),可以在运行时动态地分配和释放内存,灵活地创建和操作数组。

指针与const  (仅c99)

指针是const

  • 表示一旦得到了某个变量的地址,不能再指向其他变量

            int *const p = &i ;   ----->   p -->i 的关系是 const

所以可以:*p = 12 ;

但不可以:p ++ ;

所指是const

  • 表示不能通过这个指针去修改那个变量(并不能使那个变量成为 const )

           const int *p = &i ;   -----> *p 是 const , i = 26 ( ok ) , p = &j ( ok );

判断哪个被 const 了的标志是 const 在 * 的前面还是后面

  • const 在 * 前 :*p 被 const ,不可改变
  • const 在 * 后 : p -->i 的关系被 const

转换

可以把一个非 const 转化为 const 

void f ( const int* x ); //是一种guarantee
int a = 15 ;
f ( &a ) ; //ok
const int b = a ;
f ( &b ) ;  //ok

如上,无论原本变量是什么类型,f 函数都保证该变量在函数中为 const ,不会改变

  • 当要传递的参数的类型比地址大的时候使用:既能用比较少的字节数传递值给参数,又能避免函数对外面变量的修改

const数组

const int a [ ] = { 1 , 2 , 3 } ;     ---->  表示数组的每个单元都是 const int 

所以必须通过初始化赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值,为保证数组不被函数破坏,可以设置参数为 const 
int sum ( const int a [ ] )

指针运算

指针中“+1”代表什么

	char ac [] = {0,1,2,3,4,5,6,7,8,9,};
	char *p = ac ;
	printf ("p  =%p\n" , p ) ;
	printf ("p+1=%p\n" , p+1 ) ;      //地址+1 -->sizeof(char)=1

	

    int ai [] = {0,1,2,3,4,5,6,7,8,9,};
	int *q = ai ;
	printf ("q  =%p\n" , q ) ;
	printf ("q+1=%p\n" , q+1 ) ;	    //地址+4  -->sizeof(int)=4

用不同类型来定义数组和指针,运行后发现:

        char型:地址+1,对应sizeof(char)= 1 ;

        int型:地址+4,对应sizeof(int)= 4 ;

存放变量时,int型每四个字节存放一个变量,“+1”后要到下一个存放变量的地址处读取,如上图

ai[0]地址为‘2a’,ai[1]地址为‘2e’,如果仅加一变为‘2b’,则读不到下一个变量。

指针与数组的对应

	char *p = ac ;
	printf ("p  =%p\n", p ) ;
	printf ("p+1=%p\n", p+1 ) ; 
	
	* p    -> ac[0]  //仅用于说明两者值相同
	*(p+1) -> ac[1]
    *(p+n) -> ac[n]

char *p = ac 与 char *p = & ac [ 0 ] 意义相同 

注:要加(),提高(p+1)的优先级。

指针计算

这些算数运算可以对指针做:

  • + ,+= , - ,-= ;
  •  ++ /  -- ;
  •  两个指针相减
	int ai [] = {2,1,2,3,4,5,6,7,8,9,} ;
	int *q = &ai [ 0 ] ;
	int *q1 = &ai [ 6 ] ;
	printf ("q  =%p\n" , q ) ;
	printf ("q1  =%p\n" , q1 ) ;
	printf ("q1-q=%d\n" , q1 - q ) ;
	printf ("*q1-*q=%d\n" , *q1 - *q ) ;

结果如下:  

可看出q1与q差值的十进制数是24,即6*4(int所占字节),6代表这两个地址中间,有6个int类型的东西在;

*q1 与 *q 差值是所对应变量的差值

(两者不可混淆!)

*p++

(++优先级比 * 高) 

  • 取出p所指的那个数据,并把p移到下一个位置去;
  • 常用于数组类的连续空间操作(如遍历);

我们知道,要遍历一个数组,可以使用for循环:

	char ac[] = {0,1,2,3,4,5,6,7,8,9,} ;
	int i;
	for ( i = 0 ; i <sizeof(ac)/sizeof(ac[0]) ; i++ ){
		printf ("%d ", ac[i] ) ;	
	}

而用上*p++,可以有另一种写法: 

	char ac[] = {0,1,2,3,4,5,6,7,8,9,-1} ;	
    while( *p != -1){
		printf ("%d ", *p++ ) ;
	}

利用-1提示数组的结束

指针比较

  • < , <= , == , > , >= , != 都可以对指针做
  • 比较他们在内存中的地址
  • 数组中的单元的地址肯定是线性递增的

0地址

  • 内存中有0地址,0地址是不能随便碰的;
  • 指针不应具有0值;
  • 用0地址表示特殊的事情:
  • 1. 返回的指针是无效的;
  • 2. 指针没有被真正初始化(先初始化为0);
  • NULL是一个预定定义的符号,表示0地址;
  • (有的编译器不愿意你用0来表示0地址)

指针的类型

  • 无论指向什么类型,所有的指针的大小都是一样的,因为都是地址;
  • 不同类型的指针不能直接相互赋值:
	char ac[] = {0,1,2,3,4,5,6,7,8,9,} ;
	char *p = ac ;

	int ai[] = {2,1,2,3,4,5,6,7,8,9,} ;
	int *q = ai ;

我们知道 sizeof ( char ) = 1 , sizeof ( int ) = 4 

此时如果 p = q ,int *q =0 ,

那么 p [ 0 ] ~ p [ 3 ] = 0 .(即四个位上的变量全部为0)

不直接相互赋值是为了避免用错指针

指针的类型转换

  • void* 表示不知道指向什么东西的指针(计算时与char* 相同,但不相通);
  • 指针也可以转换类型

如:int *p = &i ;   void *q = (void*)p ;  

这并没有改变p所指的变量的类型,而是让 *q 以 void 看 p 所指的变量,即认为 i 是 void ;

总结:用指针做什么

  • 需要传入较大的数据时用作参数
  • 传入数组后对数组做操作
  • 函数返回不止一个结果
  • 需要用函数修改不止一个变量
  • 动态申请的内存

实际应用

/*题目:数根
问题描述
 对于一个正整数 n,我们将它的各个位相加得到一个新的数字,如果这个数字是一位数,
我们称之为 n 的数根,否则重复处理,直到它成为一个一位数,这个一位数也算是 n 的数根。
例如:考虑 36,3+6=9,9 就是 36 的数根。考虑 138,1+3+8=12,1+2=3,3 就是 138 的数
根。请编写程序,计算 n 的数根。
输入格式
 输入一个正整数 n.
输出格式
 输出一个整数,表示 n 的数根。
样例输入
11
样例输出
2
样例说明
 11 为 n,2 为其数根。
评测用例规模与约定
1 ≤ n ≤ 10^6*/
int gen(int *n,int*sum){
	int t;
	while(*n>0){         //注意计算时要用 *n ,n 为指针形式,不好进行数值计算
		t=*n%10;
		*sum+=t;
		*n/=10;
	}
	return 0;
}
int main()
{
	int n;
	scanf("%d",&n);
	int sum=0;
	do{
		gen(&n,&sum);         //注意 mian 函数中调用函数指针的写法:&
		n=sum;
		sum=0;
	}while(n>10);
	printf("%d",n);
	return 0;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值