C语言学习 --- 指针

指针分析

        指向对象的数据类型 *指针名;

指针名:

指针的类型:

        除了指针名剩下的就是指针的类型

指针指向对象的类型:

        除了 *指针名 剩下的就是指针指向对象的类型

int *p1;
    指针名:p1
    指针的类型:int *
    指针指向对象的类型:int
    
char *p2;
    指针名:p2
    指针的类型:char *
    指针指向对象的类型:char
    
double *p3;
    指针名:p3
    指针的类型:double *
    指针指向对象的类型:double
    
int (*p4)[3];
    指针名:p4
    指针的类型:int (*)[3]
    指针指向对象的类型:int [3]
    
void (*p5)(void);
    指针名:p5
    指针的类型:void (*)(void)
    指针指向对象的类型:void (void)

指针偏移

        指针是一个变量,所以能进行运算操作 + - ++ -- 指针偏移

        指针偏移是以指针指向对象的类型为单位进行偏移,指针指向对象的类型从指针定义上看

int a = 0x12345678;

指针在数据字节中的偏移

#include<stdio.h>

int main(void)
{
    int a = 0x12345678;
    
    int *p = &a;
    
    char *q = (char *)&a;
    
    printf("%x %x\r\n",*p,*q); 
    
    printf("%p %p %p %p\r\n",q+0,q+1,q+2,q+3);
    printf("%x %x %x %x\r\n",*(q+0),*(q+1),*(q+2),*(q+3));
    return 0;
}
int *p = ????;     p-1   --- 偏移1个int     p+1 --- 偏移1个int  高地址 			
double *p = ???;   p-1   --- 偏移1个double  p+1 --- 偏移1个double  高地址

int (*p)[3] = ???? p-1   --- 偏移3个int     p+1 --- 偏移3个int   高地址
int a[6] = {1,2,3,4,5,6};

int *p = (int *)(&a+1);   //  &a -- int (*)[6]   &a[0]  --- int *   --- 数值一样,类型不一样

p = p - 2;

printf("%d",*p);  // 5

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

int *p = (int *)(a+1);

p = p + 2;

printf("%d",*p);  // 4

两个指针指向同一个数组,指针相减得到的是什么?

        ---元素的下标之差

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

int *p = &a[1];
int *q = &a[5];
printf("%d",q-p); //4;
int a[6] = {1,2,3,4,5,6}; 
//想翻转为 6 5 4 3 2 1

int *p = a + 0;
int *q = a + 5;

// *p *q的数值交换

int temp;

for(;p<q;p++,q--)
{
    temp = *p;
    *p = *q;
    *q = temp;
}

p = a;
for(;p<=a+5;p++)
{
    printf("%d ",*p);
}

数组指针

        是指针,指针指向对象的类型是数组 (不是数组里面的首元素) --- 行指针

        使用场景:一般指向二维数组里面的一行

定义

数组类型分为两部分:

        元素的类型 元素个数

格式:

         元素的数据类型 (*指针名)[元素个数];

注意:

        定义数组指针必须使用(*指针名)括起来,如果不加就不是指针。

数组指针对于二维数组的操作和数组名对二维数组的操作形式是一样的

练习:

char names[][20] = {"lili","tom","xiaoming","lucy"};

定义一个指针指向第一行。 --- 行指针  一行的类型  char [20]
char (*p)[20];  // 定义了指针p,它是一个数组指针   4/8个字节

// 行地址
p = names;  // p = &names[0];     数组名是数组首元素的地址

printf("%s",*(p+0));   // 列地址,每一行的首列的地址
printf("%s",*(p+1));   // 列地址,每一行的首列的地址
printf("%s",*(p+2));   // 列地址,每一行的首列的地址
printf("%s",*(p+3));   // 列地址,每一行的首列的地址

总结:

        定义二维数组

        按照行类型定义了对应的数组指针。

        将指针指向二维数组里面一行。

        %s --- 每一行首列的地址。

//		练习:定义一个整型的3行3列二维数组,使用行指针对这个二维数组进行输入赋值,并且按照行进行打印数据
//int a[3][3] = {{5,9,10},{5,7,12},{-1,5,67}};
#include <stdio.h>

int main(void)
{

	int a[3][3];
	int (*p)[3];
	int i,j;
	p = a; 
	printf("请输入数值:");
	for(i=0;i<3;i++)
	{
		for(j=0;j<3;j++)
		{
			scanf("%d",*(p+i)+j);
		}

	}
	for(i=0;i<3;i++)
	{
		printf("第%d行的数为:",i+1);
		for(j=0;j<3;j++)
		{
			printf("%d  ",*(*(p+i)+j));
		}
		printf("\n");
	}
	return 0;
}

注意:

        数组指针对于二维数组的操作和数组名对二维数组的操作形式是一样的。数组指针的定义形式。

场景:

        将二维数组传入到函数中取得的时候,二维数组做函数的参数,对应的形参应该设计成数组指针。

int a[4][3] = {{5,9,10},{5,7,12},{-1,5,67},{6,7,8}};

int row = sizeof(a) / sizeof(a[0]);		
int col = sizeof(a[0]) / sizeof(a[0][0]);

void test(int (*p)[3],int r,int c)
{
    // 访问外部数组的内容
    p 占多大空间  4/8     
    // 行偏移量是多少?  // 2 
    // 列偏移量是多少?		
    int i;
    int j;
    for(i=0;i<r;i++)
    {
        for(j=0;j<c;j++)
        {
            printf("%d ",*(*(p+i)+j));
        }
        printf("\n");
    }
}

test(a,row,col);  // &a[0]  --- 行地址  形参和实参类型一致的原则 -- 对应形参设计成 行指针--数组指针

指针数组

        是数组,里面存的是指针

定义:

        指针类型 数组名[元素个数];

int a = 10;
int b = 20;

&a  &b ---- int *

// 定义一个数组用来存&a和&b的地址。

int *arr[2];

// 按照数组用 --- 数组名[下标]
arr[0] = &a;
arr[1] = &b;

printf("%d %d\r\n",*(arr[0]),*(arr[1]));

练习:

int a = 10;
int b = 20;

&a  &b ---- int *

// 定义一个数组用来存&a和&b的地址。

int *arr[2];

// 按照数组用 --- 数组名[下标]
arr[0] = &a;
arr[1] = &b;

printf("%d %d\r\n",*(arr[0]),*(arr[1]));

练习:
char s1[] = "hello";
char s2[] = "world";
char s3[] = "xiaoming";


定义一个指针数组,存储每一个字符串首字符的地址,然后通过指针数组名操作对应的字符串。

// 指针数组  --- char *
char *arr[3];

arr[0] = s1;
arr[1] = s2;
arr[2] = s3; 

printf("%c %c %c\r\n",*(arr[0]),*(arr[1]),*(arr[2]));  // h  w  x

printf("%s\n",arr[0]);
printf("%s\n",arr[1]);
printf("%s\n",arr[2]);

strcpy(arr[0],"tttt");

scanf("%s",arr[0]);

数组指针和指针数组练习:

需求1:数组指针 

 
int (*p)[3];   地址 *(p+行偏移)+列偏移量
               空间 *(*(p+行偏移)+列偏移量)					   
               
主函数有一个指令包,指令包里有10个字符串指令
用户再输入一个字符串指令,
写一个子函数,判断用户输入的字符串指令是否在指令包中,
如果在指令包中返回1,不在指令包中返回0;


主函数:
    定义一个二维数组存放10个字符串 char arr[10][20]
    输入一个字符串
    

子函数:
    函数名
    返回值类型:int 
    形参列表:2个   二维数组(指令包)    输入字符串   
        
int 函数名(char (*p)[20],char *str)
{
    // 在10个字符串内查找str字符串是否存在
    循环0 ~ 10  i
    {
        如果 strcmp(*(p+i)+0,str) == 0
        {
            return 1;
        }	
    }
    
    return 0;
}
#include <stdio.h>
#include <string.h>

int cmdIn( char (*arr)[20], char *str)
{
    int i, flag = 0;

    for (i = 0; i < 10; i++)
    {
        if (strcmp(*(arr + i), str) == 0)
        {
            flag = 1;
            break;
        }
    }

    return flag;
}

int main(void)
{
    char arr[10][20] = {
        "cmd1", "cmd2", "cmd3", "cmd4", "cmd5",
        "cmd6", "cmd7", "cmd8", "cmd9", "cmd10"
    };
    char str[20];

    printf("请输入指令:\n");
    scanf("%s", str);

    if (cmdIn(arr, str))
    {
        printf("输入的指令在指令集中\n");
    }
    else
    {
        printf("输入的指令不在指令集中\n");
    }

    return 0;
}

需求2:---指针数组

int *p[3];      

元素:p[0]  p[1]  p[2]


使用指针数组存储下列字符串然后设计抽奖程序。
char s1[] = "xiaoming";
char s2[] = "xiaolan";
char s3[] = "xiaohei";
char s4[] = "xiaobai";
char s5[] = "xiaoyanzi";
char s6[] = "xiaohuamao";
char s7[] = "xiaotu";
char s8[] = "xiaomei";

定义一个指针数组
将字符串的首地址放入到指针数组中去

随机一个随机数做下标
通过%s  和  数组名[下标]  获取获奖人的姓名
#include<stdio.h>
#include<string.h>
#include<time.h>
#include<stdlib.h>
#include<conio.h>

int Getnumber(void);
int main (void)
{
//
    /*使用指针数组存储下列字符串然后设计抽奖程序。*/
    char s1[] = "小明";
    char s2[] = "小蓝";
    char s3[] = "小黑";
    char s4[] = "小白";
    char s5[] = "小燕子";
    char s6[] = "小花猫";
    char s7[] = "小兔";
    char s8[] = "小美";
    char *p[8]={s1,s2,s3,s4,s5,s6,s7,s8};
    int a;	
    
    a=Getnumber();
    printf("%s\n",p[a]);					

    return 0;
}


/*******************************************************
*函数名  :Getnumber
*功能描述:获取随机数
*返回值  :int number
*形参列表:无
*********************************************************/
int Getnumber(void)
{
    int number;
    srand((unsigned)time(NULL));
    number = rand()%8;
    return number;
}
#include <stdio.h>  
#include <stdlib.h>  
#include <time.h>  
  
int main() {  
    char s1[] = "xiaoming";  
    char s2[] = "xiaolan";  
    char s3[] = "xiaohei";  
    char s4[] = "xiaobai";  
    char s5[] = "xiaoyanzi";  
    char s6[] = "xiaohuamao";  
    char s7[] = "xiaotu";  
    char s8[] = "xiaomei";  
   
    char *names[] = {s1, s2, s3, s4, s5, s6, s7, s8};  
   
    srand(time(NULL));  
    
    int i = rand() % 8;  
  
    printf("获奖人是: %s\n", names[i]);  
  
    return 0;  
}

函数指针

        是指针,指向的对象是函数, 指向的对象类型是函数类型。

补充:

        函数类型:除了 函数名和函数体 剩下的就是函数的类型

        函数地址:函数名

定义

它是一个指针按照指针定义格式定义的

函数不一样,函数的类型就不一样,定义的函数指针就不一样。

返回值类型 (*指针名)(形参列表);

int add(int a,int b)

{

return a + b;

}

函数类型:int (int a,int b)     int (int,int)

函数地址:add

// 定义函数指针

int (*p)(int,int);   //  内存开4/8字节的空间用来存储 返回值类型是int,形参有2个都是int型的函数。

// 让函数指针指向add函数。

p = add;

// 通过函数指针调用函数    函数指针名(实参列表);

int res = p(10,20);

printf("%d",res);

使用总结

1、先分析函数类型

2、函数地址

3、定义函数指针

4、函数指针指向函数

5、通过函数指针调用函数

注意:

        函数地址用函数指针接收,函数指针就是用来存储对应函数的地址。

场景

        在什么情况用函数指针?

        将一个函数传入到函数内部执行的时候,传什么?怎么传?

        传函数名 --- 函数地址 -- 实参

        函数来说形参和实参类型要一致,形参用 函数指针。

        结合函数调用的过程理解。

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

int dis(int a,int b)
{
    return a - b;
}
    
    
接口函数或者回调函数,传递add就执行add,传递dis就执行dis

void test(int (*p)(int,int))
{
    // 调用指向的函数
    int res = p(10,20);
    printf("%d",res);
}

int main()
{
    test(add);
    test(dis);
}
    
    
场景应用拓展:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));

指针函数

是函数,返回一个地址,一个指针。

指针类型 函数名(形参列表

{

        返回的地址必须是外部的地址或者是动态申请的空间

        xxxxx不能返回局部变量地址xxxxx

}

库函数:

         char *strcat( char *str1, const char *str2 );

指针函数的使用

#include<stdio.h>

char *MyStrCat(char *str1,const char *str2) 
{
    printf("str1,str2:%p %p\r\n",str1,str2);
    
    // 拼接	
    // 先找到str1的\0
    int i = 0;
    while(*(str1+i) != '\0')
    {
        i++;
    }
    // 在找str2里面的字符放入到str1里面去
    int j = 0;
    while(*(str2+j) != '\0')
    {
        *(str1+i+j) = *(str2+j);
        j++;
    }	
    // 最后补\0 
    *(str1+i+j) = '\0';
    
    return str1; 
}

int main(void)
{
    char s1[30] = "hello";
    char s2[] = " world";
    
    printf("s1,s2:%p %p\r\n",s1,s2);
    
    char *p = MyStrCat(s1,s2);  // 0061FE82
    printf("%s\r\n",p);
    printf("%s\r\n",s1);
    return 0;
}

二级指针

        指向指针的指针,通常用来改变1级指针的指向。

定义:

        指针类型 *指针名

int a = 30;
int b = 50;
int *p = &a;

int **q;
q = &p;

//通过二级指针q将p的指向指向b.

// *q === p空间
*q = &b;

printf("%d",*p);   // b -- 50

场景:

        和函数结合。

        在一个函数内部希望修改外部指针的指向。

void test(int **q)
{
    // *q  =---- 外部的指针空间p
    
    // 动态内存申请函数u malloc(字节数)  会返回空间首字节的编号。  -- void *  失败返回NULL.
    
    *q = (int *)malloc(8);
}


int main()
{
    int *p;
    
    test(&p);
    
    // 操作p指向的空间
    *p = 20;			
    *(p+1) = 30;

NULL和void*

NULL 定义的宏  值0  --- 保留区域,不允许用户操作。

①定义一个指针不知道指向,可以先将指针赋值为NULL.

②当指针指向的空间被销毁的时候,要将指针赋值为NULL.

void *类型的指针可以接收任意类型的地址。

malloc返回值也是一个void *

memset

  • 9
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值