基础4.2 :指针扩展(指针连续定义+数组指针)

1. 指针变量连续定义

注意:两个连续定义,都必须带*号,表示指针;

   int* p,*q;

完整代码见001——sequence——pointer.c

#include <stdio.h>
int main(){
int n = 10;
    int* p,*q;
    p = &n;
    q = &n;
printf("*q = %d; *p = %d\n",*p,*q);

}
    
10,10

2. 变量指针 vs 数组指针

变量指针:指向单个变量的指针。
数组指针:指向数组的指针。

   #include <stdio.h>
    
    int main () {
        int n = 10;
        int *p;
        p = &n; // p指针指向变量
        printf("*p = %d\n",*p);
        
        int arr[] = {1,2,3,4,5,};
        p = arr;// p指针指向数组
        printf("*p = %d\n",*p);
        printf("*(p+1) = %d\n",*(p+1));
        printf("*(p+2) = %d\n",*(p+2));
        printf("*(p+3) = %d\n",*(p+3));
        printf("*(p+4) = %d\n",*(p+4));
        
        return 0;
    }
    
10,
1,
2,
3,
4,
5

指针既可以指向一个基本类型变量又可以指向一个数组。所以在使用时要注意分辨。

2.1 指针和一维数组

数组名

           int a[5];//a是数组名,5是数组元素的个数,元素就是变量
           int a[3][4];//3行4列,a[0][0]是第一个元素,a[i][j]实际表示第i+1行第j+1列,即4行5列
           则是a[3-1][4-1] == a[2][3]  也就是在第二行第三列,注意不要弄混淆了。
           
           int b[5];
           a = b; //error 因为a是常量
           printf("%#x",&a[0]); 以16进制输出第一个元素a[0]的地址
           printf("%#x",a);和printf("%#x",&a[0]);输出的结果是一样的,都为0X12FF6C

(1)一维数组名是个指针,它指的是一维数组第一个元素的地址
(2)下标和指针的关系:
 如果p是个指针变量,则
 p[i]永远等价于*(p+i)
(3)确定一个一维数组需要几个参数
 [如果一个函数要处理一个一维数组,则需要接受该数组的哪些信息]
如下面的例子所示:需要两个参数:
数组的第一个元素的地址
 数组的长度

example1:

				#include <stdio.h>
				//f函数可以输出任何一个一维数组的内容
				void f(int *pArr, int len)    //这里的len表示数组的长度 
				{
					int i;
					for (i=0;i<len,++i)        //*pArr = *(pArr+i)
						printf("%d",*(pArr+i));  // *pArr *(pArr+1) *(pArr+2) *(pArr+3)
					printf("\n");
				}
				int main(void)
				{
					int a[5] = {1,2,3,4,5};
					int b[6] = {-1,-2,-3,4,5,-6}
					int c[100] = {1,99,22,33};
 
					f(a,5);   //a是int *   且 a 等同于 &a[0]
					f(b,6);
					f(c,100);
					
					return 0;
				}
example2:
				#include <stdio.h>
				void f(int * pArr, int len)
				{
					pArr[3] = 88;   //pArr[3]等价于*(pArr+3)
				}
				int main(void)
				{
					int a[6] = {1,2,3,4,5,6};
					printf("%d\n",a[3]);   //输出的结果是4
					f(a,6);
					printf("%d\n",a[3]);   //输出的结果是88
					return 0;      
				}
4,88 符合函數調用的規則;

2.2 二维指针

2.2.1基础概念

指针用来存放地址的;二维指针用来存放指针地址的;
int a[3][4];

数组名和指针的关系;
a;//代表数组首行地址,一般用&a[0][0]的地址表示
&a[i];代表了第i行的地址,一般用&a[i][0]的地址表示
&(a[i]+j);//代表了第i行第j个元素地址,a[i]就是j==0的情况
a[i][j];//代表了第i行第j个元素
&a[i][j];//代表了第i行第j个元素的地址

*a;//代表数组a首元素也就是a[0]或者&a[0][0]
*(a+i);//代表了第i行首元素的地址,*a是i=0的情况
*(a+i)+j;//代表了第i行j个元素的地址
**a;//代表a的首元素的值也就是a[0][0]
((a+i)+j);//代表了第i行第j个元素

int a=10; //地址为&a
int p=&a; //指针地址为&p 如果是p就是a的地址
int **p1=&p; //指针地址为&p1 如果是
p1就是&p
char (p)[3];//定义了一个数组指针,指向了一个长度为3的一维字符数组 ;
有具体数字的话,需要和列一致
char
(p)[3];//定义了一个数组指针,指向了一个二维数组
//注意 char
p[3];不是一个指针,它是一个数组,里面存放了指针,

总结:对于指针类型和指针指向数据的类型,我们可以用一种简单的办法快速得出:
1.得出指针的类型:去掉指针变量则就是指针的类型;即 int* a; 类型int*
2.得出指针指向数据的类型:去掉就是我们得出的数据类型; 指向的东西:int** = &a; 则指向的是即int;

在这里插入图片描述

2.2.2使用场景

(1).指针数组作为参数使用二维指针

注意:	①指针数组与二维指针对应
		②指针数组的写法:int* parr[] = {arr[0]....arr[r]};   //指针数组的表示,最多多少行;
		③一维指针与一维数组对应
#include <stdio.h>
#include <stdlib.h>

void PrintArr2D(int** arr,int r,int c);
void PrintArray2D(int arr[][3],int r,int c);// 二维数组传参


void PrintArray2D(int arr[][3],int r,int c){
	for(int i=0;i<r;++i){
		for(int j=0;j<c;++j){
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}

}
// 场景一:指针数组作为参数使用二维指针
void PrintArr2D(int** arr,int r,int c){
	for(int i=0;i<r;++i){
		for(int j=0;j<c;++j){
			printf("%d ",arr[i][j]);
		}
		printf("\n");
	}
}



int main(int argc,char** argv){

	int arr[][3]={1,2,3,4,5,6};
	PrintArray2D(arr,2,3);
	// 指针数组与二维指针对应
	// 一维指针与一维数组对应
	int* parr[] = {arr[0],arr[1]};  //指针数组的表示
	//printf("%p",arr);
	PrintArr2D(parr,2,3);

}

// 场景二:作为①参数②返回值
// 从终端读取多个数字,返回数组和对应数字个数

#include <stdio.h>
#include <stdlib.h>

void Print(int* arr,int n);
int Read(int** arr){
	int cnt = 0;
	int num;
	int* p = NULL;
	while(EOF != scanf("%d",&num)){
		++cnt;
		p = realloc(p,cnt*sizeof(int));
		p[cnt-1] = num;
	}
	*arr = p;
	Print(p,cnt);
	return cnt;
}
void Print(int* arr,int n){
	for(int i=0;i<n;++i){
		printf("%d ",arr[i]);
	}
	printf("\n");
}


int main ()
{
    int* arr;
    int n = Read(&arr);
    Print(arr,n);
}

倘若用一维指针的结果:

在这里插入图片描述

2.2.3 二维指针的解引用以及拓展(改变外部变量,解引用)

1.3.1 改变外部变量

	函数改变外部变量的两个条件
	 1. 传指针
	 2. 解引用
	二维指针作为参数,修改外部指针,解除原来的对应关系;
#include <stdio.h>
#include <stdlib.h>
// 函数改变外部变量的两个条件
// 1. 传指针
// 2. 解引用
// 二维指针作为参数,修改外部指针
void Func(int** p){
	*p = malloc(sizeof(int));// 改变指针,则没有和n的对应关系;
	**p = 10;
}

int main(){
	int n = 0;
	int* p = &n;
	printf("p = %p\n",p);
	Func(&p);
	printf("p = %p\n",p);
	printf("*p=%d\tn=%d\n",*p,n);
}
p = 0x7ffee05ce66c
p = 0x1e6f270
*p=10	n=0

这段代码的功能是创建一个整数变量n,并将其值设置为0。然后,我们定义了一个指向n的指针p。
在main函数中,我们调用函数Func,并将p的地址传递给它。在Func函数中,我们接受一个int**类型的参数p,然后通过p = malloc(sizeof(int));这行代码来改变p的值,使其指向一个新的动态分配的整数变量的地址。由于p是指向n的指针,所以p就代表了n。
最后,我们使用printf()函数输出p和n的值。由于我们在Func函数中修改了p的值,所以输出的结果是p的值发生了变化,但是n的值仍然是0。

1.3.2 解引用–数组操作

(1)二维指针一级解引用是指针;二级解引用是普通变量;
(2)一个指针第一次出现带*,再次出现不带*,都表示指针;
(3)中间变量进行操作,指针替换进行变换
		int* q = malloc(sizeof(int)*5);
		*pp = q;

在这里插入图片描述

#include <stdio.h>
#include <stdlib.h>


void Func(int** pp){
	int* q = malloc(sizeof(int));
	*q = 10;
	*pp = q; // 二维指针解引用,对应一维指针

	// 二维指针一级解引用是指针
	//*pp = malloc(sizeof(int));
	// 二维指针二级解引用是普通变量
	//**pp = 10;
}

void Func2(int** pp){
	int* q = malloc(sizeof(int)*5);
	q[0] = 10;
	q[1] = 11;
	q[2] = 12;
	q[3] = 13;
	q[4] = 14;
	*pp = q; // 二维指针解引用,对应一维指针,指针对应指针;
}


int main(){
	int* p = NULL;
	//int** pp;
	//pp = &p;// 二维指针赋值
	printf("p = %p\n",p);
	Func2(&p);
	printf("p = %p\n",p);
	for(int i=0;i<5;++i){
		printf("p[%d]=%d\n",i,p[i]);
	}
}

NULL
0x1e3c270
10
11
12
13
14

这段代码的功能是创建一个整数指针p,并将其值设置为NULL。然后,我们定义了一个函数Func2,它接受一个int**类型的参数pp,并通过*pp = malloc(sizeof(int)*5);这行代码来改变p的值,使其指向一个新的动态分配的整数数组的地址。

最后,我们使用printf()函数输出数组的所有元素。由于我们在Func2函数中修改了p的值,所以输出的结果是数组的元素发生了变化,p的值也发生了修改。

2.3 指针变量的运算

指针变量不能相加 不能相乘 也不能相除 只能相减
如果两个指针变量指向的是同一块连续空间中的不同存储单元
则这两个指针变量才可以相减

example01:

					#include <stdio.h>
					int main(void)
					{
						int i = 5;
						int j = 10;
						int * p = &i;
						int * q = &j;
						int a[5];
						p = &a[1];
						q = &a[4];
						printf("p和q所指向的单元相隔%d个单元\n",p-q);   //并没有实际的意义
						return 0;
					}
				------输出结果为:p和q所指向的单元相隔3个单元,12个字节;--------------------------

一个指针变量到底占用几个字节
预备知识:
sizeof(数据类型)
功能:返回值就是该数据类型所占的字节数
例子:sizeof(int) = 4 sizeof(char)
1 sizeof(double) = 8

sizeof(变量名)
功能:返回值是该变量所占字节数
假设p指向char类型变量(1个字节)
假设p指向int类型变量(4个字节)
假设p指向double类型变量(8个字节)
p q r本身所占的字节数是一样的;

总结:
一个指针变量,无论它指向的变量占几个字节,该指针变量本身只占4个字节;32bit计算机;
一个变量的地址使用该变量首字节的地址来表示

					#include <stdio.h>
					int main(void)
					{
						char ch = 'A';
						int i = 99;
						double x = 66.6;
						char * p = &ch;
						int * q = &i;
						double * r = &x;
						printf("%d %d %d\n", sizeof(p), sizeof(q), sizeof(r));
						return 0;
					}
				------输出结果为:4 4 4---------------------------------------

详解:
硬件内存为一个单元是一个字节,一个字节是8bit,而又一个字节是一个地址编号
而x理论占8个字节,即x有8个地址编号,那么取x地址的时候到底是取的8个字节中即8个编号中哪一个来
作为x的编号呢?而最终规定使用 第一个字节的地址当做x的地址编号
而最终无论是保留首地址还是末地址作为x的地址编号,他们都占4个字节即4*8=32bit,32位系统也是cpu的线程总线数
【2的32次方=4G RAM】 cpu的2的32次方个状态,所以对应地址编号都按照32bit来进行存储,所以无论是哪个编号
它最终都只占4个字节。所以输出的结果为4 4 4

2.4 多级指针

			#include <stdio.h>
			int main(void)
			{
				int i = 10;
				int *p = &i;
				int **q = &p;
				int ***r = &q;
				//r=&p; error,因为r是int***类型,r只能存放int**类型变量
				printf("i = %d\n",***r);
				return 0;	
			}
		程序运行结果为i=10

解析:多级指针可以参考指针的定义;
***r = (&q)=(p) = i;
&q = p; 虽然写法不合理,但是可以这么理解;

3. 练习题

3.1 二维数组的排列:

(1)引用二维数组元素、对二维数组进行输入和输出,i 作为行下标, j 作为列下标,程序如下:


#include <stdio.h>
int main()
{
        int a[2][3],i,j;     
        printf("please intput by line:\n");
        for(i=0;i<2;i++)
                for(j=0;j<3;j++)
                        scanf("%d",&a[i][j]);
        printf("This is the latest putout:\n");
        for(i=0;i<2;i++)
        {
                for(j=0;j<3;j++)
                        printf("%4d",a[i][j]);
                printf("\n");
        }

        return 0;
}
解析:
1.    无关于输入的方式,而在于 2 * 3 == 6 故里面有6个元素,当输入满6个的时候,自动输出。、       2.    输入的方式有下面的几种,结果都按二维数组的方式来进行排列,i为行下标,j作为列下标
--------------------------------------------
please intput by line:
1 2 3 10 20 30
This is the latest putout:
1   2   3
10  20  30

2. please intput by line:
1
2
3
4
5
6
This is the latest putout:
1   2   3
4   5   6

3.please intput by line:
1234567890        
2
3
4
5               
6
This is the latest putout:
1234567890   2   3
4   5   6

4.please intput by line:
1 2 3 4  5
6
This is the latest putout:
1   2   3
4   5   6
-----------------------------

结论:通过以上输入方式的对比就可以看出异同了。
总结:
需要注意i为行下标,j作为列下标的循环输入,以及输出时的for嵌套语句
因为输入是先按行一个一个录入的,第一行满了就会继续进行第二行的录入。
输出的时候也是同样的,只是里面加入了换行,以保持输出数组的整齐性。

(2) 如果要先进行,列录入,后进行,行录入,然后按行输出,
改动点:scanf输入的时候是:&a[j][i]
秩序改写程序如下:

#include <stdio.h>
int main()
{
       int a[2][3],i,j;     
       printf("please intput by line:\n");
       for(i=0;i<3;i++)
               for(j=0;j<2;j++)
                       scanf("%d",&a[j][i]);   //注意j为行下标
       printf("This is the latest putout:\n");
       for(i=0;i<2;i++)
       {
               for(j=0;j<3;j++)
                       printf("%4d",a[i][j]);
               printf("\n");
       }

       return 0;
}
看结果,会完全不一样:(当然这是按列录入,按行输出)
please intput by line:
	1 2 3 4 5 6
	This is the latest putout:
1   3   5
2   4   6

(3) 如果要变成3行2列,则程序改写程序如下:

#include <stdio.h>
int main()
{
       int a[3][2],i,j;
       printf("please intput by line:\n");
       for(i=0;i<3;i++)
               for(j=0;j<2;j++)
                       scanf("%d",&a[i][j]);
       printf("This is the latest putout:\n");
       for(i=0;i<3;i++)
               {
               for(j=0;j<2;j++)
                       printf("%4d",a[i][j]);
               printf("\n");
               }
       return 0;
}
其结果为:
please intput by line:
1 2 3 4 5 6
This is the latest putout:
1   2
3   4
5   6

(4) 同样如果要成为按列录入,则如下所示:

#include <stdio.h>
int main()
{
       int a[3][2],i,j;
       printf("please intput by line:\n");
       for(i=0;i<2;i++)
               for(j=0;j<3;j++)
                       scanf("%d",&a[j][i]);
       printf("This is the latest putout:\n");
       for(i=0;i<3;i++)
               {
               for(j=0;j<2;j++)
                       printf("%4d",a[i][j]);
               printf("\n");
               }
       return 0;
}
其结果为:
please intput by line:
1 2 3 4 5 6
This is the latest putout:
1   4
2   5
3   6

3.2 二维数组与指针:

通过建立一个指针数组引用二维数组元素
int *p[3],a[3][2],i,j;
for(i=0;i<3;i++)p[i] = a[i];
这里等号右边的a[i]是常量,表示a数组每行的首地址,等号左边的p[i]是指针变量,循环执行的结果使p[0]、p[1]、p[2]分别指向a数组每行的开头,这时候数组p和数组a之间的关系如下图所示:

这样的话那么a数组元素a[i][j]的引用形式*(a[i]+j)就完全等价于*(p[i]+j)
即:
在这里插入图片描述

(p[i]+j) 等价于 (a[i]+j)
((p+i)+j) 等价于 ((a+i)+j)
(
(p+i))[j] 等价于 (
(a+i))[j]
p[i][j] 等价于 a[i][j]

example1:

#include <stdio.h>
int main()
{
      int aa[3][3] = {{2},{4},{6}},i,*p = &aa[0][0];
      for(i=0;i<2;i++)
      {
              if(i==0)
                      aa[i][i+1] = *p + 1;
              else
                      ++p;
              printf("%d",*p);
      }
      printf("\n");

      return 0;
}
23
解析:
1.     当i=0时,aa[0][1]=*p+1=2+1=3 但是最后让输出的是*p的值,所以跟这里没有关系,所以*p = 2
2.    当i=1时,++p即是地址加1,即 p=aa[0][0]的地址加1就成为了aa[0][1],而又aa[0][1] = 3,故*p = 3
3.    所以最后的结果是23

example2:

#include <stdio.h>
int main()
{
      int a[3][4] = {1,3,5,7,9,11,13,15,17,19,21,23};
      int (*p)[4] = a,i,j,k = 0;
      for(i=0;i<3;i++)
              for(j=0;j<2;j++)k+=*(*(p+i)+j);
      printf("%d\n",k);
      return 0;
}
60
解析
1.    首先来看a[3][4]的初值排列,这里千万不要弄错。三行四列否则后面算出来的结果肯定会不一样。
    1       3     7      9
    9     11   13    15
    17  19    21   23
    ---------------------
2.    k+=*(*(p+i)+j) 其实就等价于 k = k + *(p[i]+j) = k + p[i][j] = k + a[i][j]
3.    那么再由两次的for循环可以看出其实就是求 三行两列 的值的和即:1+3+9+11+17+19=60
4.    如果跟上面2后面的式子来算也是如此即:
    k = k + a[i][j]
    k = k + a[0][0] = 0+1 = 1
    k = k + a[0][1] = 1+3 = 4
    k = k + a[1][0] = 4+9 = 13
    k = k + a[1][1] = 13+11 = 24
    k = k + a[2][0] = 24+17 = 41
    k = k + a[2][1] = 41+19 = 60

example3:

#include <stdio.h>
void sub(int n, int uu[])
{
      int t;
      t = uu[n--];
      t+=3*uu[n];
      n++;
      if(t>=10)
      {
              uu[n++]=t/10;
              uu[n]=t%10;
      }
      else
              uu[n]=t;
}

int main(void)
{
      int i,n,aa[10]={0};
      scanf("%d%d%d",&n,&aa[0],&aa[1]);
      for(i=1;i<n;i++)
              sub(i,aa);
      for(i=0;i<=n;i++)
              printf("%d",aa[i]);
      printf("\n");
      return 0;
}

3
2
1
2 7 2 1
输入3 2 1其最终结果为2721
解析:
1. 这里题中一定要注意[n–]和[n++]因为他们都在用过自己本身的值之后才再次加1的
2. 所以解题如下:
当main函数中的第一个for循环的i=1时;
那么定义函数sub中n=1,而uu[]其实就是aa[]
则有:
t=uu[1]=1;
t=1+3uu[0]=7;
n=1;
uu[1]=7即aa[1]=7;
当main函数中的第一个for循环的i=2时:
那么定义函数sub中n=2,而uu[]依然就是aa[]
则有:
t=uu[2]=0; //因为在开始的时候有aa[10]={0}赋过初值
t=0+3
uu[1]=21;
n=2;
uu[2]=2.1;
uu[3]=21%10=1;
最终因为是以%d来进行输入,所以aa[2]=2 aa[3]=1
3. 那么最终循环输出aa数组的前3个数组(包含第三个元素)元素依次为:
aa[0] = 2
aa[1] = 7
aa[2] = 2
aa[3] = 1
所以最终的结果为2721

example4:

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

      return 0;
}
3fe5d190
3fe5d1b4
解析:次数相差9个单位,36个字节;转化为16进制:0x24;
所以新地址是3fe5d1b4
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值