「C语言回顾之旅」第一篇:指针详解-阿里云开发者社区

开发者社区> 香飘叶子> 正文

「C语言回顾之旅」第一篇:指针详解

简介:
+关注继续查看

说明:

    最近学校课程开设了《数据结构》的课程,无疑,数据结构的重要性在IT生涯的进阶路上是不可置疑的,也常说,数据结构是专业性与非专业性的分界线。所以无论以后走的是什么方向,毕竟是读计算机专业的,所以必须学好数据结构的。虽然目前我给自己定的方向是走运维/系统架构方向的,可有句话说得好,不懂开发的运维注定会被淘汰,在IT这一行,要让自己变得更加强大。最近也一直在学Python,感觉还不错,学数据结构相信对自己也肯定有好处的,对一些较为底层的知识有些了解和理解,这样才能走得更远!

    无疑C语言就很重要了,而在C语言当中,数组/指针/结构体/链表等这些都是非常重要的,所以根据往前自己的学习经验,需要把C语言中的指针之后等更深入的知识好好再回顾和总结一遍,为学好数据结构打下坚实的基础吧!





一.指针变量


1.定义指针变量


·变量的指针包含两方面信息:指向变量的地址和指向存储单元的数据类型;

·定义指针变量需要指定基类型,方法如下:

1
int *pointer;

·基类型说明存储单元的数据类型,指针的值即是所指向变量的地址;

·注意下面的一个赋值为非法的:

1
*pointer = 100;    ===>左右类型不一致,赋值非法

·基于内存的地址编址可知,内存的值是无符号整型;

·为指针变量赋初值时注意是否有乱指的情况(即指向程序不授权的地址,会出错),会有下面三种情况:

1
2
3
1.正确指向本程序的变量    ===>正确
2.指向其它程序未授权的地址    ===>非法
3.未赋初值,只有随机值,指向未知地方    ===>非法

·可看下面一个非法的示范:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
 
void swap(int *p1, int *p2)
{
  int *p3;        ===>定义p3指针变量时已有初值,为随机值
  *p3 = *p1;    ===>修改p3指向的值,会出现“未授权”的情况,即程序试图去修改一个未知地方的值
  *p1 = *p2;            这显然是会引起错误的。
  *p2 = *p3;
}




2.引用指针变量


·两个重要的符号:

1
2
&    取地址运算符,&a取变量a的地址
*    指针运算符,*p取指针p所指向的变量的值

·常用的三种指针引用:

(1)对指针变量赋值

1
pointer = &a    ===>把a的地址赋给指针变量,或让pointer指向a

(2)引用指针变量指向的值

1
printf("%d",*pointer);    ===>输出a的值

(3)直接引用

1
printf("%d",pointer);    ===>直接输出指针变量的值,是一个内存地址




3.指针变量作用之一:作函数参数


·编写一个交换两数值的程序,用指针实现,代码如下:

a.main函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main(void)
{
  int a,b;
  int *p1,*p2;
  printf("Please input a:");scanf("%d",&a);
  printf("Please input b:");scanf("%d",&b);
  printf("a=%d, b=%d\n",a,b);
  p1 = &a;p2 = &b;
 
  swap(p1,p2);
  printf("Now, a=%d, b=%d\n",a,b);
  return 0;
}

b.swap函数代码如下:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
 
void swap(int *p1, int *p2)
{
  int temp;
  temp = *p1;
  *p1 = *p2;
  *p2 = temp;
}

·编译,链接与执行:

1
2
3
4
5
6
xpleaf@leaf:~/stuc$ gcc -o swap swap_main.o swap_sec.o
xpleaf@leaf:~/stuc$ ./swap
Please input a:3
Please input b:4
a=3, b=4
Now, a=4, b=3





二.指针与数组


1.数组元素的指针


·所谓数组元素的指针就是数组元素的地址;

·数组的引用有下标法和指针法,在编译时,最终数组引用的实现都是通过指针实现的;

·数组名实际为一地址常量(指针常量),指向数组的第一个元素,该地址值不可改变;

·可以以指针的操作方式来引用数组中的元素;




2.通过指针运算引用数组元素


·只讨论加减:

1
2
3
p1+1:表示指向下1个元素,指针移动基类型个字节
p1-1:表示指向上1个元素,指针移动基类型个字节
p1+2:表示指向下1个元素,指针移动(基类型 * 2)个字节

·a[3]中,[ ]实际是取址运算符,等价于:*(a+3);

·通过指针运算操作,编写一个遍历数组的程序:

a.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main(void)
{
  int a[10], *p, i;
  for(i = 0;i < 10;i++)
    a[i] = i + 10;
 
  p = a;
  for(i = 0;i < 10; p++, i++)
    printf("a[%d]=%d  ", i, *p);
    printf("\n");
  return 0;
}

b.执行过程:

1
2
3
4
xpleaf@leaf:~/stuc/shuzu$ gcc -c value.c 
xpleaf@leaf:~/stuc/shuzu$ gcc -o value value.o 
xpleaf@leaf:~/stuc/shuzu$ ./value 
a[0]=10  a[1]=11  a[2]=12  a[3]=13  a[4]=14  a[5]=15  a[6]=16  a[7]=17  a[8]=18  a[9]=19




3.数组名作函数参数


·显然与指针变量作函数参数是一致的,只是换个写法,即在定义或调用函数时,下面的功能是一致的:

a.创建函数时形式不一样,实际传递的还是指针:

1
2
3
void swap(int a[])
等价于
void swap(int *a)

b.引用函数时,放入的都是指针:

1
swap(a)    ===>实际放入的都是指针

·原理为:在编译时,最终数组引用的实现都是通过指针实现的;




4.指针与多维数组


--多维数组元素的指针


·创建一个如下的二维数组进行分析:

1
a[5][2] = {{12}, {34}, {56}, {78}, {910}};

·只从文字数字上看,二维数组与指针的关系会比较抽象,可用下面的示意图形象化:

wKioL1Xz4biSEAwSAADKus0Yv_Y343.jpg

·作如下重要说明:

a.二维数组名实则是一个指针变量,包含了指向的存储单元和数据类型;

b.二维数组名指针变量指向的数据类型为:一维数组名,即指针变量;

c.所以二维数组名应为多重指针,即指向指针的指针;

d.前面说[ ]实则为取址运算符,则a[1],即为对多重指针a作运算:*(a+1),从而得到多重指针a指向的第2个指针变量;

e.*(a+1)或者说a[1]实则就是普通的一维数组名,也是一个指针,因此一维数组是如何处理的,即可对其作相应处理;

f.**(a+1)或者a[1][0],在这里就是对应的值3;

g.无论中间的指针指向如何复杂,根据数组的特性,最终的元素(在这里是数值),在内存中的存储空间是连续的。



--指向多维数组元素的指针变量


·根据一维数组和二维数组的特性,定义指向多维数组的指针变量时,有如下的两种方式:

wKioL1Xz6aih17HXAAE3tFtIrPM745.jpg

a.定义二维数组指针所指向的一维数组指针变量,与前面指针变量指向一维数组是类似的;

b.定义指向二维数组名的二维数组指针变量,下面重点讨论;


·编写程序,通过指向二维数组的多重指针变量的运算来遍历二维数组:

a.代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
int main(void)
{
  int a[5][2] = {
                {1,2},
                {3,4},
                {5,6},
                {7,8},
                {9,10}
                };
  int (*p)[2], i, j;
  p = a;
 
  for(i = 0;i < 5; i++)
    for(j = 0;j < 2;j++)
      printf("a[%d][%d]=%d\n", i, j,*(*(p+i)+j));
 
  return 0;
}

b.执行过程如下:

1
2
3
4
5
6
7
8
9
10
11
xpleaf@leaf:~/stuc/shuzu2$ ./value2 
a[0][0]=1
a[0][1]=2
a[1][0]=3
a[1][1]=4
a[2][0]=5
a[2][1]=6
a[3][0]=7
a[3][1]=8
a[4][0]=9
a[4][1]=10

·注意的是不能对p执行“(*p)++”操作,*p表示一个指针变量,值为地址常量,执行(*p)++,即要改变地址常量,这是非法的;

·可以执行p++操作,因为改变的只是变量p的值,即相当于重新给p赋值;

·一个有趣的现象如下:

a.在上面代码中添加如下一行代码:

1
printf("print %d\n", *(*p+3));

b.会多出如下输出:

1
print 4

·虽然*p表示的是第一个一维数组,最多应该只有2个元素,而上面输出的是第二个一维数组的第二个元素;

·由此也可以知道数组元素的在内存中的连续性,以及对多重指针p执行操作时对取值的影响;



--用指向一维数组的指针作函数参数


·实则可以用指向一维数组元素和指向一维数组的指针作函数参数,注意二者的不同,这里只讨论前者;

·编写一个程序,用指向一维数组的指针变量作函数参数,遍历多维数组:

a.main函数代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
int main(void)
{
  int a[5][2] = {
                {1,2},
                {3,4},
                {5,6},
                {7,8},
                {9,10}
                };
  int (*p)[2], i,j;
  p = a;
 
  fun(p, 5);
  return 0;
}

b.fun函数代码如下:

1
2
3
4
5
6
7
8
9
#include<stdio.h>
void fun(int (*p)[2], int n)
{
  int i, j;
 
  for(i = 0;i < n;i++)
    for(j = 0;j < 2;j++)
      printf("a[%d][%d]=%d\n",i, i, *(*(p+i)+j));
}

c.执行过程如下:

1
2
3
4
5
6
7
8
9
10
11
12
xpleaf@leaf:~/stuc/shuzu3$ gcc -o value3 value3.o value3_fun.o
xpleaf@leaf:~/stuc/shuzu3$ ./value3 
a[0][0]=1
a[0][0]=2
a[1][1]=3
a[1][1]=4
a[2][2]=5
a[2][2]=6
a[3][3]=7
a[3][3]=8
a[4][4]=9
a[4][4]=10





三.指针与字符串


1.字符串引用方式


·字符串实则是用数组存放,因此有两种方式可以引用字符串;


--常规方式:通过格式声明%s引用


·编写一个程序,通过%s引用字符串:

a.代码如下:

1
2
3
4
5
6
7
#include<stdio.h>
int main(void)
{
  char string[] = "I hope CL can be my girlfriend.";
  printf("%s\n",string);
  return 0;
}

b.执行过程如下:

1
2
3
4
xpleaf@leaf:~/stuc/str$ gcc -c str1.c 
xpleaf@leaf:~/stuc/str$ gcc -o str1 str1.o
xpleaf@leaf:~/stuc/str$ ./str1 
I hope CL can be my girlfriend.

·通过%s可以输出字符串是因为:string代表字符串的首地址,即string数组的首地址,且后面默认添加'\0'标记;


--字符指针变量方式


·编写一个程序,通过指针变量实现上面的功能:

1
2
3
4
5
6
7
#include<stdio.h>
int main(void)
{
  char *string = "I hope CL can be my girlfriend.";
  printf("%s\n",string);
  return 0;
}

·原理为把字符串首地址赋给string,string不断指向字符串中的每一个字符,直到指向字符串末尾标记“\0”结束输出;

·根据上面分析,下面三种方法功能一致:

1
2
3
4
5
6
*string = "I hope CL can be my girlfriend.";    ===>直接定义初始化,让string指向字符串首地址
等价于
char *string;    ===>先定义char指针类型
string = "I hope CL can be my girlfriend."    ===>让string指向字符串首地址
等价于
char string[] = "I hope CL can be my girlfriend.";    ===>通过数组方式实现

·因为数组最终通过指针实现,上面三种方法实则一致;




2.字符指针作函数参数


·这与前面的一维数组遍历实则是一致的,无非就是对指针的引用,不再涉及;

·需要注意的是:字符数组(字符串)可以通过批量方式赋值(在初始化时才行),但数值型的数组则不可以;




3.指针与字符串的注意事项


--字符数组与字符串变量区分

·字符数组存放每一个字符,字符串变量只是存放一个地址;


--关于赋值方式

·可以对字符指针变量赋值,但不能对数组名赋值,以下赋值方式是非法的:

1
2
char str[14];
str = "I hope CL can be my girlfriend.";    ===>非法赋值

·非法赋值原因在于,在定义一维数组str时,数组名str已经为一地址常量,因此往后再对其赋值是非法的;

·但下面的初始化是可以的:

1
char str[14] = "I hope CL can be my girlfriend.";

·其它初始化方式上面已有提及,不再讨论;


--关于存储单元

·对数组分配若干个存储单元以存放不同元素,对字符指针变量则分配1个存储单元,大小可实际编译器;

·判断指针变量大小,可先定义指针变量,再用sizeof或strlen(不包括'\0')即可;


--关于值是否可变

·可看下面的例子分析:

1
2
3
4
char a[] = "I hope CL can be my girlfriend.";
char *b = "I hope CL can be my girlfriend.";
a[2] = 'I';    ===>合法
b[2] = 'I'    ===>非法

·关于此内容,可用相关Python中的知识进行理解;


小结:

    数组和字符指针变量最终在实现上还是有所区别的,数组会创建每一个变量,因此数组元素的值是可变的(对变量再进行赋值),有数组时通过指针操作实则是操作数组中的每一个变量,这些变量被赋值为字符常量;指针直接指向字符串某个字符,中间并没有变量,由于字符串常量都不可改变的,因此不能通过此方式修改字符串。

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
阿里云服务器怎么设置密码?怎么停机?怎么重启服务器?
如果在创建实例时没有设置密码,或者密码丢失,您可以在控制台上重新设置实例的登录密码。本文仅描述如何在 ECS 管理控制台上修改实例登录密码。
4029 0
C语言指针5分钟教程
指针、引用和取值 什么是指针?什么是内存地址?什么叫做指针的取值?指针是一个存储计算机内存地址的变量。在这份教程里“引用”表示计算机内存地址。从指针指向的内 存读取数据称作指针的取值。指针可以指向某些具体类型的变量地址,例如int、long和double。
653 0
阿里云服务器端口号设置
阿里云服务器初级使用者可能面临的问题之一. 使用tomcat或者其他服务器软件设置端口号后,比如 一些不是默认的, mysql的 3306, mssql的1433,有时候打不开网页, 原因是没有在ecs安全组去设置这个端口号. 解决: 点击ecs下网络和安全下的安全组 在弹出的安全组中,如果没有就新建安全组,然后点击配置规则 最后如上图点击添加...或快速创建.   have fun!  将编程看作是一门艺术,而不单单是个技术。
3965 0
C语言及程序设计提高例程-7 返回指针的函数
贺老师教学链接  C语言及程序设计提高 本课讲解 返回指针的函数 #include &lt;stdio.h&gt; int *max(int *x, int *y) { int *t; if(*x &gt; *y) t = x; else t = y; return t; } int main() { int a, b
752 0
阿里云服务器如何登录?阿里云服务器的三种登录方法
购买阿里云ECS云服务器后如何登录?场景不同,阿里云优惠总结大概有三种登录方式: 登录到ECS云服务器控制台 在ECS云服务器控制台用户可以更改密码、更换系.
5715 0
指针,c语言关键字,作用域,生命周期,内存数据存储
 作用域:变量可被访问的有效范围。 生命周期:变量存储空间的有效生存时间。 extern int c;   通过关键字extern来告诉程序int c;是别的程序中定义的。 extern int fun(void); 表示这个方式是在其它的文件中定义的。 全局变量在程序的任何地方都可以被调用到,生命周期是从程序启动到程序
1041 0
阿里云ECS云服务器初始化设置教程方法
阿里云ECS云服务器初始化是指将云服务器系统恢复到最初状态的过程,阿里云的服务器初始化是通过更换系统盘来实现的,是免费的,阿里云百科网分享服务器初始化教程: 服务器初始化教程方法 本文的服务器初始化是指将ECS云服务器系统恢复到最初状态,服务器中的数据也会被清空,所以初始化之前一定要先备份好。
2902 0
+关注
110
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
文娱运维技术
立即下载
《SaaS模式云原生数据仓库应用场景实践》
立即下载
《看见新力量:二》电子书
立即下载