C语言学习笔记(浙大翁恺版)第九周(指针部分)

C语言学习笔记(浙大翁恺版)第九周(指针部分),第1张

前体知识:格式控制符%p可以输出十六进制格式的地址;%lu可以输出无符号十进制整数地址。 

9.1.1 取地址运算 运算符&

我们在第一周就见过这个符号“&”了,当时我们规定scanf("%d",&x);中变量之前一定要有这个符号

今天我们要开始学习关于它的知识,&,与加减乘除同类的运算符。

&:获得变量的地址,它的 *** 作对象必须是变量。

那地址是个什么值呢?我们用代码试一下

#include
int main()
{
int i;
printf("0x%x\n",&i);//0x开头,输出16进制数,因为c不会自动补上0x
return 0;
}

程序先给了一个warning,然后输出了0xbff12d70。

warning的提示是,如果想输出地址,不应该用%x而是%p,%p会自带0x开头输出十六进制的变量地址。

因此我们将上面打印一句改为

printf("%p",&i);

此时两种方式输出的值完全相同。然而这个地址很像一个整数,十六进制下的整数。那么试想一下我们先将地址的整数赋值给变量,再输出这个变量的值,得到的结果还一样吗?

#include
int main()
{
int i=0;
int p;
p=(int)&i;//注意这里必须要强制转换为int类型,因为取地址实际上是指针类型

printf("0x%x\n",p);//输出整数p
printf("%p\n",&i);//输出地址

return 0;
}

结果如下:可以看到结果是一样的 (32位,即x86下编译运行时相同,64位下正常的地址是八字节,int是四字节)

因此地址的大小是否与int相同取决于编译器、架构。

&不能取的地址

&不能对没有地址的东西取地址

p=(int)&(p+i);
p=(int)&(++i);
p=(int)&(i++);

这三种都不能编译,必须是一个明确的变量

试试&能取的地址
  • 变量地址(前面已试过,可行)
  • 相邻变量(连续定义)的地址(地址也相邻)
  • sizeof(&变量)(前面已试过,可行)
  • 数组的地址
  • 数组单元的地址
  • 相邻数组单元的地址
int i=0;//最好初始化这样能确定其地址
int p;
//相邻变量
printf("%p\n",&i);
printf("%p\n",&p);

 结果如图:

十六进制的c=12,因此两变量地址相差4,即一个int的字节大小。我们此时是在32位架构下编译,因此说明了,相邻变量在内存中的地址也是相邻的。

接下来试试取数组的地址:

#include
int main()
{
int a[10];

printf("%p\n",&a);
printf("%p\n",a);
printf("%p\n",&a[0]);
printf("%p\n",a[0]);
printf("%p\n",&a[1]);

return 0;
}

 

对于第二行的输出值,我们直接使用了a,上次我们直接使用数组名还是对于数组的赋值

int a[10]=0,b[10];
b=a; 

这样的错误代码进行说明,然而在上方输出框中可以看到,直接拿a输出地址同样成功,通过了编译并且和取地址之后再输出的值相同。

 a[0]和a[1]的地址相差4,为一个int的值。

9.1.2 指针 

看了上面的取地址符的各种应用,你可能会想:我学这个有什么用?

如果能将变量地址取出,然后传递给一个函数,是否能通过这个变量的地址实现在函数中访问变量呢?

有没有感觉似曾相识:scanf("%d",&i);

我们将一个地址传给scanf,它可以从其中取得我们输入的整数并放到变量i中。那么它是如何做到的呢?它是如何保存变量的地址的呢?什么样的变量可以接收取地址符得到的地址呢?

指针

一个指针类型的变量就是保存地址的变量。

int *p=&i; p是一个指针,指向一个int类型,并把i的地址交给p(也就是p的值=i的地址)

int i;
int* p=&i;//将i的地址交给p,或者说 p指向i,此时p的值=i的地址
int* p,q;//星号*靠近int
int *p,q;//星号*远离int
//二者的意思都一样,都表示p是一个指针指向int,*p是int类型,q也是int类型

通常我们用p来表示指针,p是英文point的缩写。

指针变量

指针变量的值是内存的地址,与之相对的,普通变量的值就是实际的值

 

作为参数的指针(函数中使用指针)
void f(int *p);
//函数在被调用时可以得到某个变量的地址
int i=0;f(&i);
//在函数里可以通过这个指针访问外面的局部变量i
void f(int *p);
int main()
{
int i=6;
printf("&i=%p\n";&i);
f(&i);

return 0;
}

void f(int *p)
{
printf(" p=%p\n",p);
printf("*p=%d\n",*p);

}

此时我们就可以在函数中访问变量i,意味着我们可以读写i,读是完成了,那写呢?

引入一个新运算符:*。*是一个单目运算符,用来访问指针的值所表示的地址上的变量。

*p:访问对应地址上的变量

  • *p可以做左值也可以做右值。
  • 左值:放在复制号左边,实际上是表达式计算的结果,是一个特殊的值。
  • 因此如果*p为左值时,代表了将右边的计算结果赋值给对应地址上的变量。如原变量为i=6,*p=&a;*p=5,则此时i的值更改为5.

printf中的*p作为一个整体可以看作一个整数

输出结果为:

之前我们说,函数的调用时发生的参数的转移,是一种值的传递,传值进函数后就与原来变量没关系了。然而当传递进去的是地址时,就可以在函数内部访问外部变量,得以修改其值。

指针运算符——& 与* 

前文提到:&是取得变量的地址,*是取得对应地址的变量。从字面上来看,二者互为对方的逆过程。事实上二者确实是互为反作用。

  • *&y:对一个地址取对应的变量==y
  • &*y:对一个指针取对应的地址==指针(因为存放变量的地址),也就是对指针取地址还是指针
传入地址 

为什么

int x;

scanf("%d",x);

没写&,但是编译没报错。

如果正好是32位架构,整数和地址所占字节一样大,编译器会认为这就是你传入的地址。因此scanf函数不能将你输入的值正确传入给变量,而是传到了别的地方。因此运行一定是会报错的。

9.1.3 指针的作用  指针应用场景1 :
void swap(int *pa,int *pb)
{
    int t=*pa;
    *pa=*pb;
    *pb=t;
}

此时只需要传入两个地址即可完成交换,原理是相同的,但传入指针可以改变原始值。而且正常函数只能返回一个值

也就是说,其作用是返回多个值,且某些值只能通过指针带回。传入的参数是需要进行编辑的变量。

指针应用场景2:在函数中与return分工返回值和状态

  • 函数通过return返回运算状态,指针返回结果。
  • 一般用return0或-1等不属于有效范围内的值表示出错(多在文件 *** 作中出现)
#include
int divine(int a,int b,int *result);
int main(void)
{
	int a=5;
	int b=2;
	int c;

	if(divine(a,b,&c))
	{
		printf("%d/%d=%d\n",a,b,c);
	}
return 0;
}

int divine(int a,int b,int *result)
{
	int ret=1;
	if(b==0) ret=0;
	else 
	{
		*result=a/b;
	}
	return ret;
}

 当被除数=0,即b=0时,函数会直接返回状态值ret为0,而函数又位于if条件处,此时不会输出任何值。当正常时就会将a/b=c输出。实现了分开返回值与状态。

这种方法的适用条件是运算中可能会出错,在C++/JAVA等语言中可以采用异常机制来解决这个问题。

指针最常见的错误
  • 定义指针变量,但没有指向任何变量就使用了指针
int i=1;
int *p=0;//给的不是地址

*p=5;
printf("%d",i);

这几个语句的意思是,在一个地址为0的内存中写入5这个值,而0这个地址不一定支持写入,因此程序大概率会出错,且具有不确定性。

9.1.4 指针与数组

将变量传递到函数中,函数得到的是变量的值。将指针传递到函数中,得到的是地址。那么把数组传递进去呢?我们用sizeof测试一下

 可以看到,在主函数中数组的大小就是每个数组元素占据的空间之和。而在函数中数组大小正好为一个地址/指针的大小。

warning信息提示说:函数里的数组sizeof返回的是int*的大小而不是int[]的大小

我们再查看下数组在主函数和函数的地址:

 可以看到无论在主函数还是函数中,数组a的地址相同。也就是说传到函数里的a就是外面的数组。

我们再试试,如果在函数里直接更改数组的值,到主函数里会不会发生改变?

 于是综合一下前面的测试,我们得出结论:传递到函数的数组实际上是个指针!这也就解释了,为什么在函数的参数表中数组要留个括号,为什么在方括号里写上数组大小也没用。

那它既然是个指针,直接在函数头写指针如何?

没问题!编译运行都ok!warning都没有

 数组作为函数参数

以下四种写法等价:

有没有一种可能,数组和指针之间有一种联系?

确实有:

数组变量是特殊的指针
  • 数组变量本身其实是一种地址
    • 在取数组地址时不需要用到&,直接写数组名就可以。
    • 但是在取数组单元的地址时需要&,因为数组单元表示变量

 因此我们在第一节取地址符中a==&a[0]

  • []运算符可以对数组做,也可以对指针做(1)
  • * 运算符可以对指针做,也可以对数组做(2)

(1)

int min=1;
int *p=&min;

printf("*p=%d",*p);
printf("p[0]=%d",p[0]);

结果为: 

其含义是:p的值为min的地址,此时p指向min所在的空间,p[0]意思是假设指向的是min[1],一个只有一项的数组。输出的就是所指的地址上面的第一个整数。

(2) 

#include 
int main()
{
	 int a[]={1,2};
	 printf("*a=%d\n",*a);
	 return 0;
}

虽然a是数组,但想输出*a依然可以得出其值。直接当指针用就好了。

  • 数组变量是const的指针,所以不能被赋值
  • 这也就解释了为什么不能将一个数组直接赋值给另一个数组
int a[]={1,2,3};
int b[]; == int *const b;

 const加在这里意味着b是一个常量,而常量的定义是:一旦被创建后,其值就不能再改变。不能被改变,创建并初始化这个数组之后就不能再去代表别的数组了。

所以,数组是一个常量指针。

9.1.5 指针与const

C语言const的用法详解,C语言常量定义详解 (biancheng.net)http://c.biancheng.net/view/2041.html可以作为参考资料

常量的定义是:一旦被创建后,其值就不能再改变。

const是一个修饰符,加在变量前面,这个变量就不能被修改。指针也是一个变量,然而指针指向的也是变量。这两者都可以加上const限制。

指针是const

指针不能修改,也就是不能再得到地址

指针指向的变量是const

不能通过指针修改

p指向别人,ok

i自增,赋值,ok

通过p更改i的值,不行

变量可以直接 *** 作,但不能通过指针间接 *** 作

这些变化的区别
  •  int i;
  • const int *p1 = &i;
  • int const *p2 = &i;
  • int *const p3 = &i;

 const在*号前,表示指针指向的变量不能修改。即第一种和第二种含义一致。

 const在*号后,表示指针不能被修改。

转换const与非const

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

const数组

const int a[]={1,2,3,4,5,6};

数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int

所以必须通过初始化进行赋值

保护数组值

当数组传递进指针时,传递的是数组地址,因此函数中可以修改数组的值

为保护数组不受破坏,可以设置参数为const

int sum(const int a[],int length);

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/793196.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-05
下一篇 2022-05-05

发表评论

登录后才能评论

评论列表(0条)

保存