首页 > 编程语言 >C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针

C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针

时间:2024-07-27 19:28:48浏览次数:13  
标签:p2 变量 指向 int C语言 详解 数组 指针

写在最前,一篇文章学会C语言指针

顶级重要,学习C语言最重要的一部分-------指针

第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针

文章目录

1.指针变量

1.1 指针变量的定义

C语言有两种变量:
1.变量(普通变量)存储内容值;
2.地址变量(指针变量)存储地址值;

定义的格式:
类型名 指针变量名
如:
int a,b,*p1,*p2;
float x,y,*p3,*p4;
char s,*p5,*p6;

注:
(1) 定义变量(普通变量,指针变量)都必须在前面有类型名。
(2)在定义指针变量时,指针变量名前的*表示现定义的是一个指针类型的变量。星号并不是指针变量名的一部分,只是一个标志

用户变量名是_,数字字母组成的,不能用*开头
但是指针变量是*开头,说明我们要定义的是指针变量

(3) 指针变量专门用来存地址,禁止将一个整型值直接赋给一个指针变量。

int a,b,*p;
a=1000;
b=2000;
p=4000;  //非法

1.2 指针变量的引用

"&"取地址运算符,通过&运算可以取出普通变量的地址。
“*”指针运算符,、*可以取出指针变量所指向的普通变量的值,(间接引用普通量)

例子:

*指针变量名
如:
int a,b=20,c=30;
int d=40,*p;
p=&d;
a=*p;
a=d;

重点补充:画图做指针相关题目:
在c语言的题目中,关于,数组,static,指针,链表,文件操作,我们要画图,来保证正确

上面的例子可以画图表示为,指针画箭头指向,普通变量放入小房子

本小节注意:
(1)可以通过赋值使一个指针变量“指向”某一普通变量(指针变量=&普通变量)
例如:

int a=10;
int *p;
p=&a

(2)在C语言中正确的做法是先让指针变量指向一个确定的存储单元后,再通过该指针变量引用它所指向的存储单元。
例如:

int *p;
*p=200; 危险!!!

从形式上来说虽然没错,但是如果不给指针变量初始化,指针变量随机拿地址的话,可能会拿到系统区的地址,修改系统区的值,就会让操作系统紊乱所以不合法

(3)变量名(普通变量,指针变量)都表示其存储单元内的值。
p1=p2; // p1指向了p2所指向的单元。

(4)若指针变量p指向变量a,即将变量a的地址赋给了指针变量p。

如:int a=20,*p=&a;
则有下列结果:
1. *p是a
2. p是&a
3. &*p是&a是p
4. *&a是*p是a
5. (*p)++是a++
6. (*p)--是a--
7. ++(*p)是++a ++*p
8. --(*p)是--a --*p

(5)所有的指针变量在内存中分配的字节数相同。sizeof() 2字节

例题:
1.请输出下列程序的结果

void fun(int *x,int *y)
{
	printf("%d %d",*x,*y);
	*x=3;
	*y=4;
}
main()
{
	int x=1,y=2;
	fun(&y,&x);
	printf("%d %d ",x,y);
}

结果如下:
2 1 4 3

注意形参,实参

2.请输出下列程序的结果

#include<stdio.h>
void swap(int *p1,int *p2)
{
	int temp;
	temp=*p1;
	*p1=*p2;
	*p2=temp;
}

main()
{
	int a,b;
	int *p1=&a,*p2=&b;
	scanf("%d%d",p1,p2);
	swap(p1,p2);
	printf("%d,%d",*p1,*p2);
}

假设输入 2 5
画图解决:
在这里插入图片描述

输出:5 2

3.请输出下列程序的结果

#include<stdio.h>
void swap(int *p1,int *p2)
{
	int *temp;
	temp=p1;
	p1=p2;
	p2=temp;
}

main()
{
	int a,b;
	int *p1=&a,*p2=&b;
	scanf("%d%d",p1,p2);
	swap(p1,p2);
	printf("%d,%d",*p1,*p2);
}

在这里插入图片描述
输出:2 5

4.请输出下列程序的结果

#include<stdio.h>
void swap(int *p1,int *p2)
{
	int *temp;
	*temp=*p1;
	*p1=*p2;
	*p2=*temp;
}

main()
{
	int a,b;
	int *p1=&a,*p2=&b;
	scanf("%d%d",p1,p2);
	swap(p1,p2);
	printf("%d,%d",*p1,*p2);
}

实际不合法,函数中的指针变量,没有初始化,可能会随机指向系统区,让计算机崩溃

2.指向数组的指针变量

若数组做为形参,则将数组名做指针变量来处理。

指向数组元素的指针变量:
由于数组元素与普通一样,所以定义指向数组元素的指针变量与定义指向普通变量的指针变量完全一样。

2.1 指向一维数组的指针变量

注:
(1)在c语言中规定:数组名代表数组的首地址,而且是一个地址常量
(2)当指针变量指向数组中的某一个元素时,指针变量+1后指向数组的下一个元素,指针变量减1时指向数组中前一个元素。
(3)当指针变量指向数组时,下标运算([])用于数组也可用于指针变量后
1️⃣*(p+I) *(a+i) a[i] p[i]
2️⃣ p++ *(p++) ++的优先级高于
3️⃣(*p)++ ++(*p)

例子1:
若有定义:int b[5]={10,30,20,15,40};
int *q=b+2; //q最开始指向20

则:
*q++   表达式的值?,同时q指向了?   20 b[3]
*++q   表达式的值?,同时q指向了?   15 b[3]
(*q)++ 表达式的值?,同时q指向了?   20 b[2]
++(*q) 表达式的值?,同时q指向了?   21 b[2]

例子2:

举例指针在数组中的使用
main()
{
int *p,a[3],I;
p=a;
for(i=0;i<3;i++)
{
	scanf("%d",p++);
}
printf("\n\n");
for(p=&a[0];p<a+3; )
	printf("%d ",*p++);

(4)若两个指针变量指向同一个数组,则这两个指针变量可以进行大小比较如:
char s[10];
char *p=s+3,*p2=&s[7];

可以做差,等到的两个数组的距离差

(5)在形参中的数组实际上是一个指针变量,并不是真正的数组,因为该"数组名"的值是可以改变的,而真正的数组名的值是不能改变的。

(6)若形参是数组或指针变量,则在函数中可以通过该形参改变实参的值

2.2 指向二维数组的指针变量(难)

二维数组的指针分为两类,一类是列地址(列指针),一类是行地址(行指针),列地址控制的是具体的行列,行地址一次控制一整行,移动是行移动。
在这里插入图片描述

若a是一个二维数组,则有:

(1)a+i是行指针,即指向的是一整行,若对它加1则指向下一行。说的是行指针
(2)*(a+i)和a[i]一样,都是一个列指针即指向的是一个元素 说的是列指针
(3)*(a+i)+j和a[i]+j一样,都表示元素a[I][j]的地址。即与&a[i][j]等价
*(a+i)+j ,a[i]+j, &a[i][j] 说的是元素a[I][j]的地址,3种地址表示
(4)*(*a+i)+j),*(a[I]+j),(*(a+i))[j]和a[i][j]一样,都表示元素a[i][j]

小技巧:
二维数组的引用,肯定存在一个*一个[]或[][]或两个*,不能出现只有一个的情况
int (*)[3] 是行指针的类型,int *是列指针的类型
例如:
int w[2][3],则对w数组元素非法引用是
A. *(w[0]+2) 第一行第三个元素的值
B. *(w+1)[2] 越界不合法
C. w[0][0]
D. *(w[1]+2)
E. w[1]+2 秒杀不合法

2.2.1 int *p类型

如:

int a[3][4];
int *p=&a[0][3];

则:
p+1指向元素a[1][0];
p+4指向元素a[1][3];
p-2 指向元素a[0][1];
常用于取二维数组a元素地址的方式:
&a[i][j],a[i]+j,*(a+i)+j

2.2.2 int (*p)[m]类型

行指针,一次加减指向一行

如:

int a[5][7];
int (*p)[7];
p=a;

char b[10][80];
char (*r)[80];
r=b+5;
main()
{
	int a[3][4]={1,2,3,4,5,6,7,8,9,10,11,12};
	int (*p)[4];
	p=a;
	for(i=0;i<3;i++)
	{
		for(j=0;j<4;j++)
			printf("%d",p[i][j]);
		printf("\n");
	}
}
int a[3][4]={{1,2,3},{4,5,0},(*pa)[3];
int i;
pa=a;
for(i=0;i<3;i++)
	if(i<2)pa[1][i]=pa[1][i]-1;
	else pa[1][i]=1;
	printf("%d\n",a[0][1]+a[1][1]+a[1][2]);

输出7

3.指向字符串的指针变量

字符串常量:C语言对字符串常量是按首地址处理字符串常量

意味着双引号“”要当作地址!!!

给出几个例子,初始指向字符串的指针变量
(1)char str[]=“china”; 正确,字符串数组的初始化
(2)char *p=“china”; 正确,指针变量后接一个地址“”
(3)str=“Chinese” ; 错误,str为字符串常量,不能这么修改
(4)p=“Chinese”;正确,指针变量修改指向Chinese
(5)char *p ={“china”};错误,指针后面要加地址,如果是花括号就识别不出来了,以为是初始化,必须是双引号
(6)char str[]={“china”};正确,花括号可以省略
(7)char *p;*p=“china”;错误,字符串常量不能修改

例子:

# include<stdio.h>
# include<string.h>
int main()
{
    char s[20]="abcdefGHI";
    char p[20]="1234567";
    printf("%s\n",s+2); //cdefGHI
    printf("%s\n",p+5); //67
    printf("%d\n",strlen(p+2));//5
    printf("%s\n",strcpy(p+3,s+3));//defGHI 从p+3开始,p+3是当前地址
    printf("%s\n",p);    //123defGHI
}

4.指向函数的指针变量

函数名与数组名一样,是起始地址,而且是一个地址变量。

定义指向函数的指针变量的方式
类型名 (*指针变量名) () ;

来看一个具体的实例:

int min(int a,int b)
{
	return a>b?b:a;
}
int max(int a,int b)
{
	return a>b?a:b;
}
int main()
{
	int x=6,y=10;
	int (*p)();
	p=max;
	printf("%d",max(x,y));
	printf("%d",p(x,y));
	p=min;
	printf("%d",min(x,y));
	printf("%d",p(x,y));
}

5.指针数组和指向指针的指针变量

5.1 指针数组

若一个数组的所有元素均为指针类型(地址),则称为指针数组。

格式:
类型名 *数组名[常量表达式]
int *s[10];

其实就是10个 int *s类型
与二维数组行指针形式辨析 int (*p)[10]; 多个括号。

例子:

# include<stdio.h>
# include<string.h>
int main()
{
    char ch[3][4]={"123","456","78"},*p[3];
    int i;
    for(int i=0;i<3;i++)
    {
        p[i]=ch[i];
    }
    for(i=0;i<3;i++)
    {
        printf("%s",p[i]);
    }
}

在这里插入图片描述

输出结果:12345678

5.2 指向指针的指针变量

用来存放指针变量地址的指针变量称为指向指针的指针变量。
定义格式:
基类型名 **指针变量名

例如:

int a=3;
int *p=&a;
int **k=&p;
则,*k得到变量p(变量a的地址)
**k得到变量a的值(a的数据3)

6.空指针

指针变量可以有空值,即指针变量不指向任何变量,不指向任何有用的存储单元

在系统中已将NULL定义为0,即NULL的值为0.
int a,b,c,*p=NULL;
此时p的值为空指针,即p不指向任何有用的存储单元。尽管NULL的值为0,但我们不能认为p指向了地址为0的存储单元。

注:
(1)当一个指针变量的值为空指针时,我们不能引用它所指向的存储单元。
(2)若某指针(地址)的基类型为void型,则有引用时应进行相应的强制类型置换。

看一个非法的例子:

//错误例子,编译错误,原因是null没有房子
int main()
{
	int a[]={1,2,3,4,5,6,7,8,9,10,11,12};
	int *p=a+5,*q=NULL;
	*q=*(p+5);
	printf("%d %d\n",*p,*q);
}

标签:p2,变量,指向,int,C语言,详解,数组,指针
From: https://blog.csdn.net/weixin_62613321/article/details/140565138

相关文章

  • C语言的函数递归
    一、递归的意义所谓函数递归,就是在某个函数中再次调用这个函数本身,做到函数自己调用自己,这个就是函数的递归。而函数的递归主要是的作用是将一个本身比较复杂,并且步骤繁多的函数逐次的递归使其变得简单化,就比如剥笋:我们想要得到里面能吃的部分,就需要剥笋。而笋的皮有很多层,每......
  • 入门C语言Day18——break&continue&goto语句
    前面的博文中有提到do-while与for循环语句,其中的流程图中有break和continue这两个部分还没解释。所以今天先来解释一下break与continue语句。break和continue两个关键字都被运用在循环中。break的作用是永久的终止循环,只要break被执行,直接就会跳出循环,继续往后执行。......
  • c++ typedef 关键字详解
    在C++中,typedef关键字用于为已有的数据类型创建一个新的别名。这使得代码更加易读和维护,尤其是当使用复杂的类型定义时。typedef可以用来简化代码或使其更具描述性。基本语法typedefexisting_typenew_name;这里,existing_type是已有的类型,new_name是你为它创建......
  • Midjourney入门-局部重绘详解与具体操作
    局部重绘是Midjourney一个非常实用的功能。通过局部重绘你可以对Midjourney生成的图片进行二次修改,达到控图改图的效果。接下来我们讲下操作step1-图片生成先生成一组图片,并挑选一张step2-认识三种重绘功能在生成的图片底下有三个按钮:Vary(Subtle)-变化(微妙)通......
  • 写一个函数返回参数二进制中1的个数(c语言)
    1.int一共有32位,我们要知道一个数取余2等于1(n%2==1),就可以得到二进制中1的个数.然后一个数除于2(n=n/2),就可以使32位向右一位(例如:5为101,5/2==2,2为10,)。方法1(不可以输入负数)//写一个函数返回参数二进制中1的个数//方法1intcount(intn,intz){ //当a为正数的 if(n>......
  • 调整奇数全部位于偶数前面(c语言)
    1.方法一:第一步先建一个arr,判断arr中的奇数(arr[i]%2!=0)和偶数(arr[i]%2==0)分别打印,先打印奇数,后打印偶数。//调整奇数全部位于偶数前面//方法一voidtest(int*arr,intsz){ inti=0; for(i=0;i<sz-1;i++) { if(arr[i]%2!=0) { printf("%d......
  • Python 中的正反斜杠用法详解
    在Python编程中,字符串是一个常用的数据类型,字符串中的斜杠(反斜杠\和正斜杠/)具有特殊的用法和意义,本文将介绍这两种斜杠的用法。一、反斜杠的转义作用在Python中,反斜杠(\)被称为转义字符,它常用于两个主要目的。1.引入特殊字符反斜杠可以用来引入特殊字符序列,这些序列在Py......
  • 简单的扫雷——基于C语言的控制台小游戏
    前言:  “将大象装进冰箱要几步?--打开冰箱,把大象放进去,关上冰箱。”  同样的,该扫雷游戏的编写过程也只需三步:逻辑梳理-代码实现-运行调试。本文将使用C语言来一步步剖析并完成扫雷这一案例。一.扫雷的游戏逻辑  该扫雷的游戏逻辑为:  1.生成棋盘,并布置数个......
  • STM32的IO复用详解
    前言最近在STM32的IO复用上面吃了一个亏,所以把问题记录下来并且将IO复用的原理再深刻理解一下。起因就是自己做的项目需要用一个SPI读取数据的功能,我是用的STM32F429的HAL库,所以就将SPI的例子复制过来,之前SPI读的是开发板上面的spiflash,用的是SPI5,我自己的项目用的是SPI3,所以......
  • Spring 常用的三种拦截器详解
    前言在开发过程中,我们常常使用到拦截器来处理一些逻辑。最常用的三种拦截器分别是AOP、Interceptor、Filter,但其实很多人并不知道什么时候用AOP,什么时候用Interceptor,什么时候用Filter,也不知道其拦截顺序,内部原理。今天我们详细介绍一下这三种拦截器。拦截器顺序我们现在有......