指针

本文深入探讨了C语言中的指针概念,包括指针的使用、指针与数组、二级指针、字符串和指针数组、函数指针以及指针在函数参数传递中的作用。详细阐述了指针如何存储和访问内存地址,以及在数组、字符串和函数调用中的各种操作。此外,还介绍了指针在二维数组和函数指针数组中的应用,以及在函数参数传递中的复制传参和地址传参的区别。
摘要由CSDN通过智能技术生成

1.指针

在32位的操作系统中所有数据类型的指针大小都是4个字节
地址都是四个字节 一个字节8bit
指针存放的是地址
32位的操作系统
地址是从0x0000 0000 到 0xFFFF FFFF
一个16进制的0xF占4bit (1111)
一个字节是 8bit
因此一个字节可以用两个16进制的数表示 高4位和低四位
所以上面的地址是四个字节说明的是,8个十六进制

一、 指针的使用

  1. 指针在定义变量的时候也可以进行初始化
    前提是初始化的对象提前定义好(开辟好空间的地址)

  2. 指针不能随意指向地址 指向地址都是需要开辟好的地址

  3. 在指针中使用强制转换,转换的是该地址的类型
    int a=100;
    char *p1;
    p1 =(char *)&a; //将该地址强制转换成(char *) 因为是地址 所以加了&
    定义指针

  4. 指针变量在取内容的时候根据的是该变量类型的大小 char类型 1个字节
    int 4个字节 double则是8个字节

     int a=0x1234;
     char *p1;
     printf("%x\n",*p1);  //因为p1的类型是char类型 因此是一个字节
     p1++;//地址加加
     printf("%x\n",*p1);//取出字节
     输出结果 :  0x34   
                 0x12 
    
char *p;//这里的指针是一个野指针
p=(char *)malloc(128);//开辟128字节的空间  并强制转换成char型
memset(p,'\0',128);//给开辟的空间初始化

读取指针的内容 使用*p

printf("指针的内容是%d",*p);

读取指针的地址 使用p 因为指针本身就是一个地址

printf("指针的地址是%p",p);

二.指针和数组

数组元素的引用
方法一: 数组名加下标
例如
int a[100];
a[2]=50;

方法二:指针名加下标
int a[100];
int *p;
p=a;
p[2]=50; /* 因为p和a等价*/
/* 
*注意 p是指针变量  但是a是常量
*因此 可以通过p++,进行地址的改变 但是不能使用a++ 进行改变 
*/
上面两种得到的结果是相同的

指针数组

1.指针可以保存数组的首地址
2.可以定义一个数组,该数组中保存的是若干个相同变量的指针,这样的数组称为指针数组

定义方法
数据类型 *数组名[元素个数];
int *p[10];//里面存放了10个指针
int a=10;
int b=15;
p[0]=&a;
p[1]=&b;
printf("p[0]=%d",*p[0]);//得到的结果就是10
    /* 大多数情况下 指针数组都用来进行多个字符串的保存*/
    char *name[5]={"nihao","hello","xiaoming","zhnagsan","lisi"};
    int i;
    for(i=0;i<5;i++){
        printf("%s\n",name[i]);
    }
    

在这里插入图片描述

指针的指针–二级指针

指针的指针  即指针的地址

x是二级指针 里面保存的是p的地址
*p ====a
**x ====*p=======a
int a=0x1234;
int *p;
int **q;

p=&a;/*p指向的地址是a的地址*/
q=&p;/*q指向的地址是p的地址*/
/*指针指向的是地址*/
/*指针的指针 二级指针保存的是一级指针的地址  */
printf("a= %d %d %d \n",a,*p,**q);
printf("&a= %p %p %p \n",&a,p,*q);
printf("p= %d %d\n",p,*q);/*p表示的就是a的地址  *q表示的是取出a的地址*/
printf("&p =%p %p ",&p,q);

如果只是调用主调函数,只需要传递变量即可,不需要传递参数,
想要改变主调函数变量的值,必须传递的是变量的地址,而且通过*+地址进行赋值

指针进行使用的时候,指向的一个元素的地址,因此在进行操作的时候,需要将指针的地址传递给调用的函数
例如 char *str =“12345”;
将str的地址传递给参数,因为需要接收str的地址,需要使用二级指针进行接收 char **p, p的内容就是str传递进来的地址 而使用p就是改变str地址的内容

void xxx(int *x,int *q)
{
	printf("x= %p\n",x);/* x表示的就是一个地址 */
	int temp;
	temp=*x;
    *x=*q;
	*q=temp;
}
void fun1(char **str)
{
	printf("str =%p\n",str);//str保存的是地址,str指针的地址
	printf("str = %s\n",*str);//*str 表示的是取出地址里面的内容
    *str="qqccdc";
}
int main()
{
    int x=100;
	printf("x= %p\n",&x);
	int cc=120;
	xxx(&x,&cc);
	printf("%d %d\n",x,cc);
	int a=0x1234;
	int *p;
	int **q;
    char *str="12345";
    printf("str = %p\n",&str);
	fun1(&str);/*地址*/
	printf("%s\n",str);
	
	return 0;
}

在这里插入图片描述

在这里插入图片描述

字符串和指针

str 指向文字常量区的时候,内存里的内容不可修改 
	但是可以让str重新指向另一个文字常量区
str 指向栈、堆、静态全局区的时候,内存的内容是可以修改

使用时赋值 
字符数组:使用 scanf 或者 strcpy
 char buf_aver[128]; 
 buf_aver="hello kitty"; 错误,因为字符数组的名字是个常量   
 strcpy(buf_aver,"hello kitty"); 正确 
 scanf("%s",buf_aver); 正确 
 指向字符串的指针: 
 char *buf_point; buf_point="hello kitty"; 正确,buf_point 指向另一个字符串   
 strcpy(buf_point,"hello kitty"); 错误,只读,能不能复制字符串到 buf_piont 指向的内存里 取决于 buf_point 指向哪里。
1. 栈和全局区内存中的内容是可修改的
char str[100]=”I love C!;
str[0]=‘y’;//正确可以修改的
2.文字常量区里的内容是不可修改的
char *str=”I love C!; 
*str =’y’;//错误,I存放在文字常量区,不可修改
3.堆区中的内容可以进行修改
char *str =(char*)malloc(10*sizeof(char));
strcpy(str,"I love C");
*str='y';

在这里插入图片描述

数组指针

二维数组

分级
一个 * 为一级,一个 []一级
升级
取&升级
int b是一个元素,&b就是一个一级指针
降级
1、使用时和[]降级
int b[3];b是一级指针,b[0]是元素
2、定义时和[]升级
int b是一个元素,int *b是一个b是一个一级指针,int b[10]的b是一个一级指针或者是一维数组

指针与二维数组
int arr[2][3] = {{1,2,3},{4,5,6}};
1、 二维数组的数组名记录的是数组的行首地址

2、数组的行首地址就是第一个数组的首地址,第一数组的首地址就是第一个变量的地址

3、数组名+1跨越的是一行(一个数组)的大小


二维数组
二维数组,有行,有列。二维数组可以看成有多个一维数组构成的,是多个一维数组的集 合,可以认为二维数组的每一个元素是个一维数组。
int a[3][5]; 
定义了一个35列的一个二维数组。 
可以认为二维数组a由3个一维数组构成,每个元素是一个一维数组。

int a[4][4]={
            {1,2,3,4},
            {5,6,7,8},
            {9,10,11,12},
            {13,14,15,16}
            };
其中a[0] a[1] a[2] a[3] 为行指针 表示每一行的起始地址
a[0]-->a[0][0]  a[0]的地址实际上就是a[0][0]
a[0]+1 的地址就是 a[0][1]的地址

*(a[0]+1) 相当于表示出来的就是 a[0][1]里面的内容

使用数组指针   
int (*p)[4];
p=a;   //让p里面保存二维数组的首地址
*p 的内容就是a[0]的地址    **p就是取出a[0]地址里面的内容
因此会出现下面的一个式子
*(*(p+i)+j)  表示的就是i行 j列的元素  
例如第二行 第二列的元素
*(*(p+1)+1)
/* 二维数组a中 ,a+1 指向下个元素,即下一个一维数组,即下一行。 */
int main()
{
  int a[3][5];
  printf("a=%p\n",a);
  printf("a+1=%p\n",a+1);
}
数组指针 
本身是个指针,指向一个数组,加1跳一个数组,即指向下个数组。
 数组指针的作用就是可以保存二维数组的首地址

指向的数组的类型(*指针变量名)[指向的数组的元素个数]
int (*p)[5];//定义了一个数组指针变量p,p指向的是整型的有5个元素的数组 p+1 

往下指5个整型,跳过一个有5个整型元素的数组。
int a[3][5];
*****************************************************************
p[x][y]  等价于 *(*(p+x)+y)
*****************************************************************
p相当于是行指针  *(p)的意思是让他定位到一列和一行之上
*****************************************************************
a    他的类型是指向三个元素数组的指针  默认指向第一行
a+1   现在它指向了下一行
*(a+1) 若是int*解引用后我们拿到了int类型数据,那数组指针呢?实际上我们对一个数组的指针解引用,拿到的是一个数组。
*(a+1) == a[1]
*(a+1)+1   表示指针向右移动一个单位
*(*(a+1)+1)  表示取出这个单元的元素
***************************************************************
数组指针的使用方法
可以将二维数组的首地址传递到另一个函数里面,此时函数的形参就需要定义为数组指针

void fun(int(*p)[5],int x,int y)
{
  p[0][1]=101;
  p[x-1][y-1]=101;
}
void test2()
{
  int i,j;
  int a[3][5] = {0};
  fun(a,3,5);
  for(i=0;i<3;i++){
    for(i=0;j<5;i++){
      printf("%d ",a[i][j]);
    }
    printf("\n");
  }
}

在这里插入图片描述
在这里插入图片描述

代码

//int a[3][5];
    int a[3][5]={{22,33,44,5,77},{666,777,888,999,111},{1,2,1,3,6}};
    /*
    * 22  33  44  5   77
    * 666 777 888 999 111
    * 1    2  1    3   6
    */
	int (*p)[5];
	
	printf("a=%p\n",a);
	
	printf("a+1=%p\n",a+1);
	
	p=a;
    printf("p=%p\n",p);
    /* 显示第一行*/
    for(i=0;i<3;i++){
      printf("%d\n",p[0][i]);
    }
    p=p+1;//现在p在第二列
    /* 显示第二列的元素*/
    for(i=0;i<3;i++){
      printf("%d\n",p[0][i]);
    }
//    printf("p+1=%p\n",p+1);//p+1跳一个有5个整型元素的一维数组
            

、函数指针

使用函数指针首先要有一个定义的

int add(int a,int b)
{
   return a+b;
}

在main函数里面使用

int (*padd)(int a,int b);
padd=add;//指针地址指向函数地址
//通过指针调用函数
padd(1,2);//函数的调用方法1
(*padd)(1,2);//函数的第二种调用方法

函数指针数组

本质上是一个数组  
数组里面存放的是函数指针

int (*p[10])(int a,int b)
定义了一个函数指针数组,有10个元素p[0] ~p[9],每个元素都是函数指针变量, 
指向的函数,必须有整型的返回值,两个整型参数
**************************************************
函数指针最常用的地方 
函数指针最常用的地方在于将一个函数作为参数传递给另一个函数的时候要使用函数指针 
将一个函数作为参数传递给另一个函数,将这个函数称之为回调函数

三、 指针函数

指针函数实质是一个函数。函数都有返回类型(如果不返回值,则为无值型),只不过指针函数返回类型是某一类型的指针。
看看下面这个函数声明:

int fun(int x,int y);

这种函数应该都很熟悉,其实就是一个函数,然后返回值是一个 int 类型,是一个数值。
接着看下面这个函数声明:

int *fun(int x,int y);

这和上面那个函数唯一的区别就是在函数名前面多了一个*号,而这个函数就是一个指针函数。其返回值是一个 int 类型的指针,是一个地址。
这样描述应该很容易理解了,所谓的指针函数也没什么特别的,和普通函数对比不过就是其返回了一个指针(即地址值)而已。

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 
char *InitMemory()  //指针函数
{
	char *s = (char *)malloc(sizeof(char) * 32);  //堆空间,函数运行完不会释放
	return s;//返回的是这个堆空间 地址
}
//因为str保存在栈空间  当该函数调用消失后,申请的内存空间就会消失 因此打印返回的值为NULL
char *InitMemory()
{
//   char str[100]="hello";
   //可以使用静态变量 让其保存在静态内存区,就不会随着函数调用的结束而释放内存空间
   static char str[100]="hello";
   return str;
}
int main()
{
	char *ptr = InitMemory();
	strcpy(ptr, "helloworld");   //将helloworld 放到开辟的内存空间中
	printf("%s\n", ptr);
 
	free(ptr);
    system("pause");
	return 0;
}

指针和函数的关系

咱们可以给一个函数传一个 整型、字符型、浮点型的数据,也可以 给函数传一个地址。
函数的传参方式:复制传参、地址传参、全局传参(几乎用不到)
如果只是调用主调函数,只需要传递变量即可,不需要传递参数,
想要改变主调函数变量的值,必须传递的是变量的地址,而且通过*+地址进行赋值

1.关系一复制传参 传递参数
//函数的传参方式之复制传参:将实参的值传递给形参,不管形参怎么改变,跟实参都没有 关系
void myfun1(int a, int b) 
{
    int temp;
    temp = a;
    a = b;
    b = temp;
    printf("in fun: a = %d, b = %d\n", a, b);
    printf("&a = %p, &b = %p\n", &a, &b);
}
void test1()
{
    int a = 100, b = 20;
    myfun1(a,b);
    printf("in fun: a = %d, b = %d\n", a, b);
}
2.关系二 地址传参

例子一 地址

//函数的传递之地址传参  将实参的地址传递给形参 
//形参对保存在地址的内容进行任何操作,实参的值也会发生改变
void myfun2(int *p, int *q)
{
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;
}
void test2()
{
    int a = 100, b = 20;
    myfun2(&a,&b);
    printf("in fun: a = %d, b = %d\n", a, b);
}

例子二

int a=10;
int *p=&a;
这里的p保存的是a的地址 

//p里面存放的是字符串指针str的地址
//*p  表示的是字符串指针str地址里面保存的被指向的地址
void myfun3(char **p)
{
    //相当于 让str地址里面保存的地址 被指向了新开辟的128字节的空间
    *p = (char *)malloc(128);
}

void test()
{
    char *str=NULL;
    //这里取str的地址实际上传递的是字符串指针的地址
    myfun3(&str);
}

例子三 传递数组

数组默认进行的是地址传参

给函数传递数组的时候,没办法将数组的内容作为整体传输过去
只能将数组的首地址传输过去

//传递一维数组
//void fun1(int p[])  //方式一
void fun1(int *p)     //方式二
{
  printf("%d\n",p[2]);
  p[2]=99;
  printf("%d\n",*(p+3));
  *(p+3)=100;
}
void test()
{
  int a[10]={1,2,3,4,5,6,7,8,9,10};
  fun1(a);
  printf("%d\n",a[2]);
  printf("%d\n",a[3]);
}
输出结果 
3
4 
99 100
//二维数组
//void fun2(int a[][4])  //方式一
void fun2(int (*p)[4])   //方式二  通过数组指针
{
   printf("%d\n",p[0][2]);
   //p[x][y]  等价于 *(*(p+x)+y)
   printf("%d\n",*(*(p+1)+2));
}
void test3()
{
  int a[2][4]={1,2,3,4,
               5,6,7,8};
  fun2(a);
}

输出结果
3
7
//传递指针数组
void fun2(char **p)   //方式二  通过数组指针
{
   int i;
   for(i=0;i<3;i++)
   {
       printf("%s\n",p[i]);
   }
}
void test3()
{
  char *p[3]={"hello","world","hahaha"};
  fun2(a);
}
//void test1(int num,char str[])
void test1(int num,char **str)
{
/*
*   int *q=&a;                       char *str=NULL
*   int **p=&q;                      char **p=&str;
*   p的地址是 q指针的地址            p的地址是 str指针的地址
*   *p的意思就是取出q指针指向的地址  *p的意思就是取出str指针指向的地址
*                                    str指针指向的地址就是字符串的首地址
*/
    //str表示的是下面字符串指针的地址
    //字符串指针的地址里面保存的地址指向的NULL
    //使用malloc开辟空间需要保存开辟空间的首地址   str指针指向的地址就是字符串的首地址  因此使用*str(*p)
    *str=(char *)malloc(128);
    //现在让字符串指针的地址保存的地址是新开辟的地址空间的首地址
    
    char *str1=*str;//让指针str1的地址指向 *str 新开辟的地址 malloc的首地址
    char *str2=*str;
    char c;
    //char *str="hello world";  指向的实际上是hello world的首地址
    //这里的*str实际上是 新开辟的地址
    //给malloc开辟的空间的首地址就行赋值
    sprintf(*str,"%d",num);     
    while(*(str2+1) != '\0'){
        str2++;
    }
    while(str1<str2 && str1!=str2){
    c=*str1;
    *str1=*str2;
    *str2=c;
    str1++;
    str2--;
    }
}
int main()
{
//    char str[128];
    char *str=NULL;
    int num;
    printf("请输入一个数:");
    scanf("%d",&num);
    
    test1(num,&str);
    printf("%s",str);

main函数传递参数

int main(int argc char *argv[])

argc :是一个int类型的变量,标志命令终端输入的参数的个数
argv :是一个指针数组,用于保存每个命令终端传入的参数

指针数组通常用于多个字符串的储存
调用  argv[0] argv[1].......
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值