&
定义
- & 用于获得变量的地址,操作符必须是变量
- & 不能对没有地址的东西取地址
对“&”的读取
在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
指针与数组
以下是一些喜欢使用指针来表达数组的原因:
- 内存效率:使用指针可以节省内存空间。当需要传递一个数组作为函数的参数时,如果直接传递数组名,会将整个数组拷贝一份到函数的栈帧中,而使用指针传递数组只需要传递数组的地址,减少了内存的开销。
- 灵活性:使用指针可以更灵活地访问和操作数组元素。指针可以进行算术运算,可以通过指针进行指针偏移来访问不同位置的数组元素,这样能够方便地实现数组的遍历、查找和修改等操作。
- 动态内存分配:指针与动态内存分配紧密相关。通过使用指针和相关的内存管理函数(如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;
}