C语言——指针

一、关于内存那点事

存储器:存储数据器件
外存
        外存又叫外部存储器,长期存放数据,掉电不丢失数据
        常见的外存设备:硬盘、flash、rom、u盘、光盘、磁带
内存
        内存又叫内部存储器,暂时存放数据,掉电数据丢失
        常见的内存设备:ram、DDR
物理内存:实实在在存在的存储设备
虚拟内存:操作系统虚拟出来的内存,当一个进程被创建的时候,或者程序运行的时候都会                 分配虚拟内存,虚拟内存和物理内存之间存在映射关系。
操作系统会在物理内存和虚拟内存之间做映射。
在32位系统下,每个进程(运行着的程序)的寻址范围是4G,0x00 00 00 00 ~0xff ff ff ff
在写应用程序的,咱们看到的都是虚拟地址。
在运行程序的时候,操作系统会将 虚拟内存进行分区。
1.堆
        在动态申请内存的时候,在堆里开辟内存。
2.栈
        主要存放局部变量(在函数内部,或复合语句内部定义的变量)。
3.静态全局区
        1):未初始化的静态全局区
                静态变量(定义的时候,前面加static修饰),或全局变量 ,没有初始化的,存在 此区
2):初始化的静态全局区
        全局变量、静态变量,赋过初值的,存放在此区
4.代码区
        存放咱们的程序代码
5.文字常量区
        存放常量的。
内存以字节为单位来存储数据的,咱们可以将程序中的虚拟寻址空间,看成一个很大的一维
的字符数组
本章所接触的内容,涉及到的内存都是虚拟内存,更准确来说是虚拟内存的用户空间

二、指针的相关概念

操作系统给每个存储单元分配了一个编号,从0x00 00 00 00 ~0xff ff ff ff
这个编号咱们称之为地址 ,指针就是地址
指针变量:是个变量,是个指针变量,即这个变量用来存放一个地址编号
在32位平台下,地址总线是32位的,所以地址是32位编号,所以指针变量是32位的即4个字节。
注意:
1:无论什么类型的地址,都是存储单元的编号,在 32位平台下 都是4个字节, 即任何类型的指针变量都是4个字节大小
2:对应类型的指针变量,只能存放对应类型的变量的地址
举例:整型的指针变量,只能存放整型变量的地址
扩展
字符变量 char ch;   ch占1个字节,它有一个地址编号,这个地址编号就是ch的地址
整型变量 int a; a占4个字节,它占有4个字节的存储单元,有4个地址编号。

Int a=0x00 00 23 4f

三、指针的定义方法

1.简单的指针
数据类型 * 指针变量名;
int * p;        //定义了一个指针变量p
在 定义指针变量的时候 * 是用来修饰变量的,说明变量p是个指针变量。
变量名是 p
2.关于指针的运算符
& 取地址 、 *取值
&:获取一个变量的地址
*:在定义一个指针变量时,起到标识作用,标识定义的是一个指针变量
除此之外其他地方都表示获取一个指针变量保存的地址里面的内容

 

#include <stdio.h>

int main(int argc, char *argv[])
{
    //定义一个普通变量
    int a = 100;
    //定义一个指针变量
    int *p;

    //给指针变量赋值
    //将a的地址保存在p中
    p = &a;

    printf("a = %d %d\n", a, *p);
    printf("&a = %p %p\n", &a, p);

    return 0;
}
执行结果
a = 100 100
&a = 0060FE98 0060FE98
扩展:如果在一行中定义多个指针变量, 每个指针变量前面都需要加*来修饰
int *p,*q;        //定义了两个整型的指针变量p和q
int * p,q;        //定义了一个整型指针变量p,和整型的变量q
3、指针大小
在32位系统下,所有类型的指针都是4个字节
因为不管地址内的空间多大,但是地址编号的长度是一样的,所以在32位操作系统中,地址都是四个字节
#include <stdio.h>

int main(int argc, char *argv[])
{
    char *a;
    short *b;
    int *c;
    long *d;
    float *e;
    double *f;

    printf("sizeof(a) = %d\n", sizeof(a));
    printf("sizeof(b) = %d\n", sizeof(b));
    printf("sizeof(c) = %d\n", sizeof(c));
    printf("sizeof(d) = %d\n", sizeof(d));
    printf("sizeof(e) = %d\n", sizeof(e));
    printf("sizeof(f) = %d\n", sizeof(f));

    return 0;
}
执行结果

sizeof(a) = 4

sizeof(b) = 4

sizeof(c) = 4

sizeof(d) = 4

sizeof(e) = 4

sizeof(f) = 4

四、指针的分类

按指针指向的数据的类型来分
1:字符指针
字符型数据的地址
char *p;        // 定义了一个字符指针变量,只能存放字符型数据的地址编号
char ch;
p= &ch;
2:短整型指针
short int *p;        // 定义了一个短整型的指针变量p,只能存放短整型变量的地址
short int a;
p =&a;
3: 整型指针
int *p;        // 定义了一个整型的指针变量p,只能存放整型变量的地址
int a;
p =&a;
注:多字节变量,占多个存储单元,每个存储单元都有地址编号,c语言规定,存储单元编号最小的那个编号,是多字节变量的地址编号。
4: 长整型指针
long int *p;        // 定义了一个长整型的指针变量p,只能存放长整型变量的地址
long int a;
p =&a;
5: float 型的指针
float *p;        // 定义了一个float型的指针变量p,只能存放float型变量的地址
float a;
p =&a;
6:double型的指针
double *p;        // 定义了一个double型的指针变量p,只能存放double型变量的地址
double a;
p =&a;
7:函数指针
8、结构体指针
9、指针的指针
10、数组指针
总结:无论什么类型的指针变量,在32位系统下,都是4个字节,只能存放对应类型的变量的地址编号。

五、指针和变量的关系

指针可以存放变量的地址编号
在程序中,引用变量的方法
1:直接通过变量的名称
int a;
a=100;
2:可以通过指针变量来引用变量
int *p;        // 在定义的时候,*不是取值的意思,而是修饰的意思,修饰p是个指针变量
p=&a;        // 取a的地址给p赋值,p保存了a的地址,也可以说p指向了a
*p= 100;        // 在调用的时候*是取值的意思,*指针变量 等价于指针指向的变量
注:指针变量在定义的时候可以初始化
int a;
int *p=&a;        //用a的地址,给p赋值,因为p是指针变量
指针就是用来存放变量的地址的。
*+指针变量 就相当于指针指向的变量
指针变量只能保存开辟好空间的地址,不能随意保存地址
#include <stdio.h>

int main(int argc, char *argv[])
{
    int *p1,*p2,temp,a,b;
    p1=&a;
    p2=&b;

    printf("请输入:a b的值:\n");
    scanf("%d %d", p1, p2);//给p1和p2指向的变量赋值

    temp = *p1; //用p1指向的变量(a)给temp赋值
    *p1 = *p2; //用p2指向的变量(b)给p1指向的变量(a)赋值
    *p2 = temp;//temp给p2指向的变量(b)赋值

    printf("a=%d b=%d\n",a,b);
    printf("*p1=%d *p2=%d\n",*p1,*p2);

    return 0;
}
执行结果
请输入:a b的值:
90 80
a=80 b=90
*p1=80 *p2=90
扩展:
对应类型的指针,只能保存对应类型数据的地址,如果想让不同类型的指针相互赋值的时候,需要强制类型转换
#include <stdio.h>

int main(int argc, char *argv[])
{
    int a=0x1234,b=0x5678;
    char *p1,*p2;
    printf("%#x %#x\n",a,b);
    p1=(char *)&a;
    p2=(char *)&b;
    printf("%#x %#x\n",*p1,*p2);
    p1++;
    p2++;
    printf("%#x %#x\n",*p1,*p2);

    return 0;
}
执行结果

0x1234 0x5678

0x34 0x78

0x12 0x56

注意:
1:*+指针 取值,取几个字节,由指针类型决定的指针为字符指针则取一个字节,指针为整型指针则取4个字节,指针为double型指针则取8个字节。
2:指针++ 指向下个对应类型的数据
字符指针++ ,指向下个字符数据,指针存放的地址编号加1
整型指针++,指向下个整型数据,指针存放的地址编号加4

六、指针和数组元素之间的关系

6.1 数组元素与指针的基本关系

变量存放在内存中,有地址编号,咱们定义的数组,是多个相同类型的变量的集合,
每个变量都占内存空间,都有地址编号
指针变量当然可以存放数组元素的地址。
1 int a [ 10 ];
2 //int *p =&a[0];
3 int * p ;
4 p =& a [ 0 ]; // 指针变量 p 保存了数组 a 中第 0 个元素的地址,即 a[0] 的地址

6.2 数组元素的引用方法

方法1: 数组名[下标]
int a[10];
a[2]=100;
方法2:指针名加下标
int a[10];
int *p;
p=a;
p[2]=100;        //因为p和a等价
补充:c语言规定:数组的名字就是数组的首地址,即第0个元素的地址,是个常量。
注意:p和a的不同,p是指针变量,而a是个常量。所以可以用等号给p赋值,但不能给a赋值。
例如:int a[10]; a++就是错误的,因为a是数组名是一个地址常量
方法3:通过指针运算加取值的方法来引用数组的元素
int a[10];
int *p;
p=a;
*(p+2)=100;        //也是可以的,相当于a[2]=100
解释:p是第0个元素的地址,p+2是 a[2]这个元素的地址。
对第二个元素的地址取值,即a[2]
#include <stdio.h>

int main(int argc, char *argv[])
{
    int a[5]={0,1,2,3,4};
    int *p;
    p=a;

    //只要将数组名赋值给同类型的指针变量,则此时的指针变量与数组名可
    //以用相同的方法操作数组
    printf("a[2]=%d\n",a[2]);
    printf("p[2]=%d\n",p[2]);

    //*(a + n) <==> *(p + n) <==> a[n] <==> p[n]
    printf("*(p+2) = %d\n",*(p+2));
    printf("*(a+2) = %d\n",*(a+2));

    printf("p=%p\n",p);
    printf("p+2=%p\n",p+2);
    printf("&a[0] = %p\n", &a[0]);
    printf("&a[2] = %p\n", &a[2]);
    return 0;
}
执行结果

a[2]=2

p[2]=2

*(p+2) = 2

*(a+2) = 2

p=0060FE88

p+2=0060FE90

&a[0] = 0060FE88

&a[2] = 0060FE90

七、指针的运算

7.1 指针可以加一个整数

往下指几个它指向的变量,结果还是个地址
前提:指针指向数组的时候,加一个整数才有意义
#include <stdio.h>

//指针的运算

//指针可以加一个整数,往下指几个它指向的变量,结果还是个地址
void test1()
{
    int a[10];
    int *p, *q;
    //p和q间隔8个字节,意味着加一个整数最终移动的字节数与指针变量的类型也有关系
    p = a;
    q = p + 2;

    printf("p = %p\n", p);
    printf("q = %p\n", q);

    return ;
}

int main(int argc, char *argv[])
{
    test1();

    return 0;
}
执行结果

p = 0060FE60

q = 0060FE68

7.2 两个相同类型指针可以比较大小

前提: 只有两个 相同类型的指针 指向 同一个数组的元素 的时候,比较大小才有意义
指向前面元素的指针 小于 指向后面 元素的指针
#include <stdio.h>

//指针的运算

//两个相同类型指针可以比较大小
void test2()
{
    int a[10];
    int *p,*q;
    p=&a[1];
    q=&a[6];
    if(p<q)
    {
        printf("p < q\n");
    }
    else if(p>q)
    {
        printf("p > q\n");
    }
    else
    {
        printf("p = q\n");
    }
}

int main(int argc, char *argv[])
{
    test2();

    return 0;
}
执行结果
p < q

7.3 两个相同类型的指针可以做减法

前提: 必须是 两个相同类型的指针 指向 同一个数组的元素 的时候,做减法才有意义
做减法的结果是,两个指针指向的中间有多少个元素
#include <stdio.h>

//指针的运算

//两个相同类型的指针可以做减法
void test3()
{
    int a[10];
    int *p,*q;
    p=&a[0];
    q=&a[3];
    printf("%d\n",q-p);
}

int main(int argc, char *argv[])
{
    test3();

    return 0;
}
执行结果
3

7.4 两个相同类型的指针可以相互赋值

注意:只有相同类型的指针才可以相互赋值(void *类型的除外)
#include <stdio.h>

//指针的运算

//两个相同类型的指针可以相互赋值
void test4()
{
    int a = 100;
    int *p, *q;
    p = &a;

    printf("a = %d %d\n", a, *p);

    q = p;
    printf("*q = %d\n", *q);

    *q = 999;
    printf("a = %d\n", a);
}

int main(int argc, char *argv[])
{
    test4();

    return 0;
}
执行结果

a = 100 100

*q = 100

a = 999

八、指针数组

1、指针和数组的关系

        1:指针可以保存数组元素的地址
        2:可以定义一个数组,数组中有若干个相同类型指针变量 ,这个数组被称为指针数组
指针数组的概念:
        指针数组本身是个数组,是个指针数组,是若干个相同类型的指针变量构成的集合
        注意:一般遇到这样的叠词,本质就是后者

2、指针数组的定义方法:

类型说明符 * 数组名 [元素个数];
int * p[10];//定义了一个整型的指针数组p,有10个元素p[0]~p[9],每个元素都是int *类型的变量

int a;
p[1]=&a;
int b[10];
p[2]=&b[3];

p[2]、*(p+2)是等价的,都是指针数组中的第2个元素。
3、指针数组的分类
字符指针数组char *p[10]、短整型指针数组、整型的指针数组、长整型的指针数组float型的指针数组、double型的指针数组
结构体指针数组、函数指针数组
#include <stdio.h>

int main(int argc, char *argv[])
{
    //大多数情况下,指针数组都用来保存多个字符串
    char *name[5] = {"Follw me","BASIC","Greatwall","FORTRAN","Computer"};
    int i;
    for(i=0;i<5;i++)
    {
        printf("%s\n",name[i]);
    }

    return 0;
}
执行结果

Follw me

BASIC

Greatwall

FORTRAN

Computer

九、指针的指针 -- 二级指针

指针的指针,即指针的地址,咱们定义一个指针变量本身指针变量占4个字节,指针变量也有地址编号

int a;
int *p;
p=&a;
*p === a
int **q;
q=&p;
*q === p
**q === *p === a
int ***m;
m=&q;
*(*(*m)) === a
注意:
p q m都是指针变量,都占4个字节,都存放地址编号,只不过类型不一样而已
#include <stdio.h>

int main(int argc, char *argv[])
{
    int a = 100;

    //定义一个一级指针
    //一级指针用于保存普通变量的地址
    int *p = &a;

    //定义一个二级指针
    //二级指针用于保存一级指针的地址
    int **q = &p;

    printf("a = %d %d %d\n", a, *p, **q);
    printf("&a = %p %p %p\n", &a, p, *q);
    printf("&p = %p %p\n", &p, q);
    printf("&q = %p\n", &q);

    return 0;
}
执行结果

a = 100 100 100

&a = 0060FE9C 0060FE9C 0060FE9C

&p = 0060FE98 0060FE98

&q = 0060FE94

十、字符串和指针

字符串的概念:
        字符串就是以’\0’结尾的若干的字符的集合
字符串的存储形式: 数组、字符串指针、堆
1、 char string[100] = “I love C!”
        定义了一个字符数组string,用来存放多个字符,并且用”I love C!”给string数组初始化 ,字符串“I love C!”存放在string中
2、 char *str = “I love C!”
        定义了一个指针变量str,只能存放字符地址编号, 所以说I love C! 这个字符串中的字符不能存放在str指针变量中。 str只是存放了字符I的地址编号,“I love C!”存放在文字常量区
3、 char *str =(char*)malloc(10*sizeof(char));
        动态申请了10个字节的存储空间,首地址给str赋值。
        strcpy(str,"I love C");        // 将字符串“Ilove C!”拷贝到str指向的内存里
总结:
字符数组:
        在内存(栈、静态全局区)中开辟了一段空间存放字符串
字符串指针: 在文字常量区开辟了一段空间存放字符串,将字符串的 首地址 付给str
堆:
        使用malloc函数在堆区申请空间,将字符串拷贝到堆区
注意:
可修改性:
        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’;//正确,可以,因为堆区内容是可修改的
注意:
        str指针指向的内存能不能被修改,要看str指向哪里。
        str指向文字常量区的时候,内存里的内容不可修改
        str指向栈、堆、静态全局区的时候,内存的内容是可以修改
初始化:
        字符数组、指针指向的字符串:定义时直接初始化
                char buf_aver[]="hello world";
                char *buf_point="hello world";
        堆中存放的字符串不能初始化、只能使用strcpy、scanf赋值
                char *buf_heap;
                buf_heap=(char *)malloc(15);
                strcpy(buf_heap,"hello world");
                scanf(“%s”,buf_heap);
使用时赋值
        字符数组:使用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、二维数组

二维数组,有行,有列。二维数组可以看成有多个一维数组构成的,是多个一维数组的集合,可以认为二维数组的每一个元素是个一维数组。
例: int a[3][5];
定义了一个3行5列的一个二维数组。
可以认为二维数组a由3个一维数组构成,每个元素是一个一维数组。
回顾:
        数组的名字是数组的首地址,是第0个元素的地址,是个常量,数组名字加1指向下个元素
二维数组a中 ,a+1 指向下个元素,即下一个一维数组,即下一行。
#include <stdio.h>

int main(int argc, char *argv[])
{
    int a[3][5];
    printf("a=%p\n",a);
    printf("a+1=%p\n",a+1);
    
    return 0;
}

2、数组指针的概念:

本身是个指针,指向一个数组,加1跳一个数组,即指向下个数组。
数组指针的作用就是可以保存二维数组的首地址

3、数组指针的定义方法

指向的数组的类型(*指针变量名)[指向的数组的元素个数]
int (*p)[5];//定义了一个数组指针变量p,p指向的是整型的有5个元素的数组
p+1 往下指5个整型,跳过一个有5个整型元素的数组。
#include <stdio.h>

//定义数组指针
void test1()
{
    int a[3][5];    //定义了一个3行5列的一个二维数组
    int(*p)[5];     //定义一个数组指针变量p,p+1跳一个有5个元素的整型数组

    printf("a=%p\n",a);        //第0行的行地址
    printf("a+1=%p\n",a+1);    //第1行的行地址,a和a +1差20个字节

    p=a;

    printf("p=%p\n",p);
    printf("p+1=%p\n",p+1);    //p+1跳一个有5个整型元素的一维数组
}

int main(int argc, char *argv[])
{
    test1();

    return 0;
}
执行结果

a=0060FE50

a+1=0060FE64

p=0060FE50

p+1=0060FE64

数组指针的用法
#include <stdio.h>

//数组指针的用法
//可以将二维数组的首地址传递到另一个函数里面,此时函数的形参就需要定义为数组指针

void fun(int(*p)[5],int x,int y)
{
    p[0][1]=101;
}

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

int main(int argc, char *argv[])
{
    test2();

    return 0;
}
执行结果

0 101 0 0 0

0 0 0 0 0

0 0 0 0 0

4、各种数组指针的定义:

(1)、一维数组指针,加1后指向下个一维数组
        int(*p)[5] ;
配合每行有5个int型元素的二维数组来用
        int a[3][5]
        int b[4][5]
        int c[5][5]
        int d[6][5]
        …..
        p=a;
        p=b;
        p=c;
        p=d;
都是可以的~~~~
(2)、二维数组指针,加1后指向下个二维数组
        int(*p)[4][5];
配合三维数组来用,三维数组中由若干个4行5列二维数组构成
         int a[3][4][5];
        int b[4][4][5];
        int c[5][4][5];
        int d[6][4][5];
        这些三维数组,有个共同的特点,都是有若干个4行5的二维数组构成。
        p=a;
        p=b;p=c;
        p=d;
(3)、三维数组指针,加1后指向下个三维数组
        int(*p)[4][5][6];
        p+1跳一个三维数组;
        什么样的三维数组啊?
        由4个5行6列的二维数组构成的三维数组
        配合:
        int a[7][4][5][6];
(4)、四维数组指针,加1后指向下个四维数组,以此类推。。。。

5、容易混淆的内容:

指针 数组 :是个数组,有若干个相同类型的指针构成的集合
        int *p[10];
        数组p有10个int *类型的指针变量构成,分别是p[0] ~p[9]
数组 指针 :本身是个指针,指向一个数组,加1跳一个数组
        int (*p)[10];
        P是个指针,p是个数组指针,p加1指向下个数组,跳10个整形。
指针的 指针
        int **p;//p是指针的指针
        int *q;
        p=&q;

6、数组名字取地址:变成 数组指针

一维数组名字取地址,变成一维数组指针,即加1跳一个一维数组
        int a[10];
        a+1         跳一个整型元素,是a[1]的地址
        a和a+1   相差一个元素,4个字节
        &a就变成了一个一维数组指针,是 int(*p)[10]类型的。
        (&a) +1 和&a相差一个数组即10个元素即40个字节。

7、数组名字和指针变量的区别:

int a[10];
int *p;
p=a;
相同点:
        a是数组的名字,是a[0]的地址,p=a即p也保存了a[0]的地址,即a和p都指向a[0],所以在引用数组元素的时候,a和p等价
不同点:
        1、 a是常量、p是变量
                可以用等号’=’给p赋值,但是不能用等号给a赋值
       
        2、 对a取地址,和对p取地址结果不同
                因为a是数组的名字,所以对a取地址结果为数组指针。
                p是个指针变量,所以对p取地址(&p)结果为指针的指针。

 

8、多维数组中指针的转换:

        在二维数组中,行地址 取 * 不是取值得意思,而是指针降级的意思,由行地址(数组指针)变成这一行第0个元素的地址。取*前后还是指向同一个地方,但是指针的类型不一样了
#include <stdio.h>

//二维数组的数组名降级问题
//二维数组的数组名默认是一个行指针,加1保存下一行的首地址
//二维数组的数据名取*,表示地址的降级,意味着行指针降级为列指针,加1保存下一个元素的地址

void test3()
{
    int a[3][5];
    printf("a=%p\n",a);
    printf("a +1=%p\n",a+1);

    printf("*a =%p\n",*a);// *a变成了第0行第0列元素的地址
    printf("(*a)+1 =%p\n",(*a)+1 ); //结果为第0行第1列元素的地址
}

int main(int argc, char *argv[])
{
    test3();

    return 0;
}
执行结果

a=0060FE54

a +1=0060FE68

*a =0060FE54

(*a)+1 =0060FE58

十二、指针与函数的关系

12.1 指针作为函数的参数

咱们可以给一个函数传一个 整型、字符型、浮点型的数据,也可以 给函数传一个地址。
函数的传参方式:复制传参、地址传参、全局传参(几乎用不到)

12.1.1 复制传参 -- 传数值

#include <stdio.h>

//函数的传参方式之复制传参:将实参的值传递给形参,不管形参怎么改变,跟实参都没有关系
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;

    printf("before fun: a = %d, b = %d\n", a, b);
    printf("&a = %p, &b = %p\n", &a, &b);

    myfun1(&a, &b);

    printf("after fun: a = %d, b = %d\n", a, b);

}

int main(int argc, char *argv[])
{
    test1();

    return 0;
}
执行结果

before fun: a = 100, b = 20
&a = 0060FE8C, &b = 0060FE88
in fun: a = 6356616, b = 6356620
&a = 0060FE70, &b = 0060FE74
after fun: a = 100, b = 20

12.1.2 地址传参 -- 传地址
#include <stdio.h>

//函数的传参方式之地址传参:将实参的地址传递给形参,形参对保存的地址的内容
//进行任何操作,实参的值也会跟着改变

void myfun2(int *p, int *q)
{
    int temp;
    temp = *p;
    *p = *q;
    *q = temp;

    printf("in fun: *p = %d, *q = %d\n", *p, *q);
    printf("p = %p, q = %p\n", p, q);
}

void test1()
{
    int a = 100, b = 20;

    printf("before fun: a = %d, b = %d\n", a, b);
    printf("&a = %p, &b = %p\n", &a, &b);

    myfun2(&a, &b);

    printf("after fun: a = %d, b = %d\n", a, b);

}

int main(int argc, char *argv[])
{
    test1();

    return 0;
}
执行结果

before fun: a = 100, b = 20
&a = 0060FE8C, &b = 0060FE88
in fun: *p = 20, *q = 100
p = 0060FE8C, q = 0060FE88
after fun: a = 20, b = 100

注意:如果实参是一个普通变量,地址传参的话就需要形参是一级指针,如果实参是一个一级指针,地址传参的话就需要形参是一个二级指针,以此类推

12.2 传数组

        将数组作为参数传递给函数,不存在复制传参和地址传参,本质都是地址传参,所以在函数内部对数组进行改变,则函数执行完毕后,原本的数组也会改变,因为传递给函数的都是数组的地
#include <stdio.h>


//传一维数组
//void fun1(int p[])    //形式1
void fun1(int *p)       //形式2(常用)
{
    printf("%d\n",p[2]);
    printf("%d\n",*(p+3));
}

void test2()
{
    int a[10]={1,2,3,4,5,6,7,8};
    fun1(a);
}

//传二维数组
//void fun2( int p[][4] )    //形式1
void fun2( int (*p)[4] )     //形式2:通过数组指针
{
    //p[x][y] <==> *(*(p + x) + y)
    printf("%d\n", p[0][2]);
    printf("%d\n", *(*(p+1) + 2));
}

void test3()
{
    int a[2][4] = {1, 2, 3, 4,
                  5, 6, 7, 8};
    fun2(a);
}

//传指针数组
void fun3(char **q)
{
    int i;
    for(i=0;i<3;i++)
    {
        printf("%s\n",q[i]);
    }
}

void test4()
{
    char *p[3]={"hello","world","kitty"};
    fun3(p);
}

12.3 指针函数 -- 指针作为函数的返回值

指针函数本质是一个函数,只不过函数的返回值是一个指针
//指针函数:指针作为函数的返回值
char *fun4()
{
    //栈区开辟的空间会随着当前代码段的结束而释放空间
    //char str[100]="hello world";

    //静态区的空间不会随着当前代码段的结束而释放空间
    static char str[100]="hello world";

    return str;
}

void test5()
{
    char *p;
    p = fun4();
    printf("p = %s\n", p);
}

12.4 函数指针 - 指针保存函数的地址

咱们定义的函数,在运行程序的时候,会将函数的指令加载到内存的代码段,所以函数也有
起始地址。
c语言规定:函数的名字就是函数的首地址,即函数的入口地址 咱们就可以定义一个指针
变量,来存放函数的地址,这个指针变量就是函数指针变量。

12.4.1 函数指针变量的定义方法

返回值类型 (*函数指针变量名 )( 形参列表
int (*p)(int,int);    //定义了一个函数指针变量p,p指向的函数
    //必须有一个整型的返回值,有两个整型参数。
int max(int x,int y) { }
int min(int x,int y) { }
    //可以用这个p存放这类函数的地址。
p=max; p=min;

12.4.2 调用函数的方法

1.通过函数的名字去调函数(最常用的)
int max ( int x , int y ) { }
int main ()
{
        int num ;
        num = max ( 3 , 5 );
}
2.可以通过函数指针变量去调用
int max ( int x , int y ) { }
int main ()
{
        int num ;
        int ( * p )( int , int );
        p = max ;
        num = p ( 3 , 5 );
}

12.4.3 函数指针数组

函数指针数组:本质是一个数组,数组里面的每一个元素都是一个函数指针
返回值类型 (*函数指针变量名[函数指针的个数])(形参列表);
int(*p[10])(int,int);
定义了一个函数指针数组,有10个元素p[0] ~p[9],每个元素都是函数指针变量,指向的函数,必须有整型的返回值,两个整型参数。

12.4.4 函数指针最常用的地方

函数指针最常用的地方在于将一个函数作为参数传递给另一个函数的时候要使用函数指针将一个函数作为参数传递给另一个函数,将这个函数称之为回调函数
#include <stdio.h>

int add(int x,int y)
{
    return x+y;
}
int sub(int x,int y)
{
    return x-y;
}
int mux(int x,int y)
{
    return x*y;
}
int dive(int x,int y)
{
    return x/y;
}

int process(int (*p)(int ,int),int a,int b)
{
    int ret;
    ret = (*p)(a,b);
    return ret;
}

int main(int argc, char *argv[])
{
    int num;
    num = process(add,2,3);
    printf("num = %d\n",num);

    num = process(sub,2,3);
    printf("num = %d\n",num);

    num = process(mux,2,3);
    printf("num = %d\n",num);

    num = process(dive,2,3);
    printf("num = %d\n",num);

    return 0;
}

执行结果

num = 5

num = -1

num = 6

num = 0

十三、经常容易混淆的指针

第一组:
        1、 int *a[10];
                这是个指针数组,数组a中有10个整型的指针变量
                a[0]~a[9]
        2、int (*a)[10];
                数组指针变量,它是个指针变量。它占4个字节,存地址编号。
                它指向一个数组,它加1的话,指向下个数组。
        3、 int **p;
                这个是个指针的指针,保存指针变量的地址。
                它经常用在保存指针的地址:
                常见用法1:
                        int **p
                        int *q;
                        p=&q;
                常见用法2:
                        int **p;
                        int *q[10];
                        分析:q是指针数组的名字,是指针数组的首地址,是q[0]的地址。
                        q[0]是个int *类型的指针。 所以q[0]指针变量的地址,是int **类型的
第二组:
        1、int *f(void);
                注意:*f没有用括号括起来
                它是个函数的声明,声明的这个函数返回值为int *类型的。
       
         2、int (*f)(void);
                注意*f用括号括起来了,*修饰f说明,
                f是个指针变量。f是个函数指针变量,存放函数的地址,它指向的函数,必须有一个int型的返回值,没有参数。

十四、特殊指针

1、空类型的指针(void *)

char *         类型的指针指向char型的数据
int *            类型的指针指向int型的数据
float*          类型的指针指向float型的数据
void *         难道是指向void型的数据吗?
                  不是,因为没有void类型的变量
回顾:对应类型的指针只能存放对应类型的数据地址
void* 通用指针,任何类型的指针都可以给void*类型的指针变量赋值。主要也是用在函数的参数和返回值的位置
int *p;
void *q;
q=p 是可以的,不用强制类型转换
举例子:
有个函数叫memset
void * memset(void *s,int c,size_t n);
这个函数的功能是将s指向的内存前n个字节,全部赋值为 c。
Memset可以设置字符数组、整型数组、浮点型数组的内容,所以第一个参数,就必须是个通用指针
它的返回值是s指向的内存的首地址,可能是不同类型的地址。所以返回值也得是通用指针
注意:void*类型的指针变量,也是个指针变量,在32为系统下,占4个字节

2、NULL

空指针:
char *p=NULL;
咱们可以认为p哪里都不指向,也可以认为p指向内存编号为0的存储单位。
在p的四个字节中,存放的是0x00 00 00 00
一般NULL用在给指针初始化。

十五、main函数传参

int main( int argc, char *argv[])
argc:是一个int类型的变量,标识命令终端传入的参数的个数
argv:是一个指针数组,用于保存每一个命令终端传入的参数
#include <stdio.h>

int main(int argc, char *argv[])
{ 
    int i;
    printf("argc=%d\n",argc);
    
    for(i=0;i<argc;i++)
    {
        printf("argv[%d]=%s\n",i,argv[i]);
    }

return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
sscanf函数是C语言中一个非常常用的函数,它可以将一个字符串按照指定的格式转换成相应的数据类型。在嵌入式开发中,sscanf函数也是非常常见的,因为很多时候需要从串口或者其他外部设备中读取数据,并将其转换成相应的数据类型进行处理。下面是一些sscanf函数的使用技巧: 1. 使用sscanf函数时一定要注意格式字符串的正确性。格式字符串中的占位符必须与待转换的数据类型相对应,否则会发生未知错误。 2. 如果待转换的字符串中包含多个数据,可以使用多个占位符进行转换。例如,如果待转换的字符串为"1,2,3",可以使用" %d,%d,%d"的格式字符串进行转换。 3. 可以使用sscanf函数的返回值来判断转换是否成功。如果返回值等于待转换字符串的长度,则说明转换成功,否则转换失败。 4. 如果待转换的字符串中包含浮点数,可以使用"%f"或者"%lf"的格式字符串进行转换。 5. 如果待转换的字符串中包含十六进制数,可以使用"%x"的格式字符串进行转换。 6. 如果待转换的字符串中包含字符或字符串,可以使用"%c"或者"%s"的格式字符串进行转换。 7. 如果待转换的字符串中包含指针类型的数据,可以使用"%p"的格式字符串进行转换。 总之,在使用sscanf函数时一定要注意格式字符串的正确性,否则很容易出现转换错误的情况。同时,还应该注意sscanf函数返回值的判断,以确保转换的正确性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

放牛娃@搞IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值