C语言-指针

1,引言

int data = 10;

定义一个变量,名字叫做 data,是int类型,所以分配了4字节的内存,并且这4字节中存储的值是10 data和这4字节对应起来了

printf("%d\n",data);
从 data对应的4字节内存中取出数据,并打印出来
​
data = 30;
把30这个数据存储到 data对应的4字节内存中去
​
对一个变量的访问,总的来说就是两种:读 和 写
无论是读还是写,都需要找到这个变量对应的内存。
在计算机中,为了方便对内存的管理,以字节为单位对内存进行了编号,这个编号我们称之为地址。
​
一个int类型的变量占4字节,其实对应了4个编号,我们平常讲的地址是指4个字节中最小的那个编号
​
scanf("%d",&data);
把从键盘输入的一个整数存储到以 &data这个编号/地址开始的4字节内存中

2,指针

指针是一种数据类型

int 这种数据类型是用来保存整数的

double这种数据类型是用来保存小数的

char这种数据类型是用来保存字符的

指针这种数据类型是用来保存地址的

如果一个指针类型的变量(p)保存了某个变量的地址(data),我们就说p指向data。
我们可以通过p找到data并进行访问。
​
定义指针变量的语法:
    基类型 * 指针变量的名字;//只定义没有初始化
    或者
    基类型 * 指针变量的名字 = 某个地址;//定义并初始化
​
说明:  
    基类型是指该指针变量将要指向的那个数据的类型
    -》如果该指针变量将要指向一个int类型的变量,那么这个基类型就是int
       如果该指针变量将要指向一个double类型的变量,那么这个基类型就是double
       ......
​
    int data = 10;
    定义一个变量用来保存data的地址
    int * p = &data;//定义一个指针变量 p 并保存了 data的地址
    或者
    int * p;//定义一个指针变量 p 
    p = &data;//把 data的地址赋值给指针变量 p 
​
    上面两种写法最终的效果是一样的 -》 p指向data
​
    地址/指针变量可以用 printf("%p",....);打印
​
指针变量占多少字节?
    只和机器有关,如果是32位机器,指针变量就是4个字节
        2^32种情况,2^32字节 -> 4G内存
                如果是64位机器,指针变量就是8个字节
        2^64字节 -> 2^32*2^32 -> 2^32 * 4G内存
​
    和它的基类型无关

3, * 运算符

* 运算符 读作 指向运算符/解引用运算符

指向运算符*是一个单目运算符,只需要一个操作数,这个操作数写在 *的后面,
这个操作数是一个地址或者是一个指针变量
​
*(地址/指针变量)    
这个表达式的含义是这个地址对应的那个对象/是这个指针指向的那个对象
对象:可以是变量、函数、数组...
​
例如:
    int data = 20;
    *(&data)    合法的表达式  。
                含义是 &data 这个地址对应的那个对象 :data
​
    int * p = &data;//这个 *不是指向运算符,只是一个标识,表示这个变量p是指针类型
    *p          合法的表达式 。含义是 p指向的那个对象 : data
​
    *p = 30;//这个表达式有两个运算符 * = ,*的优先级高 -》 (*p) = 30;
            //->  把30这个数据赋值给 *p这个表达式
            //->  把30这个数据赋值给 data 
​
    printf("%d\n",*p);//打印 *p这个表达式的值 -》 打印data的值
​
    scanf("%d",&data);
    scanf("%d",p);//和上面一样的效果
​
练习:
    写一个函数,用来交换两个整数的值
​
    void swap(int *p,int *q) 
    {
        int * r;
        //以下三行代码交换的是 p和q的值
        //不能实现交换 a和b的值的功能
        r = p;
        p = q;
        q = r;
    }
​
    //正确
    void swap(int *p,int *q) // int * p = &a;
    {
        int temp;
        temp = *p;
        *p = *q;
        *q = temp;
    }
​
    int main()
    {
        int a=10,b=20;
        swap(&a,&b);//传的是地址,但是不能说传的是实参的地址
    }

4,空指针和野指针

空指针 NULL (值是0) ,表示不指向任何一段内存。

int * p = NULL;

*p = 10;//错误,不能把10存储到一块不存在的内存

野指针是指指向一块不能访问/不属于当前进程的内存
int *r;
目前为止 r是野指针,因为没有给r赋值,r就是一个随机值,这个随机值指向的内存大概率是不能访问的
*r = 某个值; //错误的,把某个值存储到 r指向的内存,但是r指向的这块内存是随机的
            //非法访问
​
-----------------
int *r;
if(1)
{
    int data = 20;
    r = &data;//此时r指向 data,可以通过 *r 去访问 data
    printf("%d\n",*p);//正确的,打印 20
}
//此时 r指向原来的data,但是data已经被销毁了(这块内存不再属于当前进程了)
//所以此时 r也是野指针
printf("%d\n",*p);//错误的,非法访问
​
void swap(int *p,int *q)
{
    int *r;
    *r = *p;//错误,r是野指针
    *p = *q;
    *q = *r;
}

5,一维数组与指针

数组各个元素都有自己的地址,并且各个元素之间的存储地址是相邻的

int a[5] = {1,2,3,4,5};
共有5个元素: a[0] a[1] a[2] a[3] a[4]    
每个元素都有自己的地址,可以这么表示: &a[0]  &a[1] &a[2] &a[3] &a[4],是相邻的(相差4)
​
请你定义一个变量保存 a[0]的地址:
    int * p = &a[0];//指针变量p保存了a[0]的地址,-》 p指向a[0]
​
在一维数组中,数组名可以当作首个元素的地址,但是当作首个元素的地址的时候,
是一个指针类型的常量(不可修改)
    a 可以当作一个指针类型的常量(不可修改),和 &a[0]的值相同,和p的值也相同。
    p是变量可以修改
​
    int *p = a;//指针变量p保存了a[0]的地址,-》 p指向a[0]
​
    p = &a[1];//正确,现在指向a[1]
    a = &a[1];//错误,因为 a是常量
​
测试打印可以发现: 
    a[1] 的地址比 a[0] 的地址数值上大4
    &a[1] - &a[0] = ?
    并不等于4,而是等于1
​
指针类型的数据做加减法,和普通的整数加减不一样,有一套自己的规则:
    p+i (p是指针,i是整数),不是简单算术运算,而是加减 i个指针p的步长。
    指针的步长:就是指这个指针的基类型所占内存长度
    int * p1;//p1的步长是4
    double *p2;//p2的步长是8
    ......
​
    p,q都是指针,并且基类型相同 
    p-q ,结果是数值差除以步长
    p+q   没有意义,乘除也没有意义
​
练习:
    int a[5] = {1,2,3,4,5};
    int *p1 = &a[0];
    int *p2 = &a[3];
​
    p1+2 -> &a[2]
    p2-2 -> &a[1]
    p2-p1 -> 3
​
所以:
    int a[5] = {1,2,3,4,5};
    int *p = a;//int *p = &a[0];
    p+1  -> &a[1]
    a+1  -> &a[1]
    *(p+1) <-> *(a+1) <-> *(&a[1]) <-> a[1]
​
    p+2  -> &a[2]
    a+2  -> &a[2]
    *(p+2) <-> *(a+2) <-> *(&a[2]) <-> a[2]
​
    .....
    ->
    *(p+i) <-> *(a+i) <-> *(&a[i]) <-> a[i]
​
所以:访问数组元素除了下标法之外,还可以用指针法
    a[i]   *(a+i) ,这两种方法无条件等价

6,数组作为函数参数

一般传两个参数,一个是数组的首地址,一个是数组元素个数

int array_sum(int a[],int n) //等价 int array_sum(int *a,int n)

{

int sum=0;

int i;

for(i=0;i<n;i++)

         {

         sum += a[i]; //等价 sum += *(a+i);

        }

return sum;

}

无论是哪中写法,形参a都不是一个数组,而是一个指针,主调函数传参时把数组的首地址传给形参a

int main()
{
    int a[] = {12,3,4,7,10,-12,34,19};
    int n = sizeof(a)/sizeof(int);
    array_sum(a,n);//实参a在此表示数组的首地址/首个元素的地址
    return 0;
}

7,数组名的含义

int a[6] ;

数组名,在c语言中有两种含义:

(1)代表整个数组

以下这么几种情况

sizeof(a) ->求数组a所占内存大小

typeof(a) ->求数组a的类型 -> int [6]

&a ->求数组a的地址 数值上和 a 、&a[0]相等,但是含义不同,步长不同

(2) 代表数组的首地址  

        首个元素的地址,并且是常量,不可修改

        形参 = 实参

8,二维数组和指针

二维数组 int a3;

有3行4列,共12个元素,每个元素都是 int类型,在内存按顺序储存。

实际上二维数组的本质也是一维数组:

有3个元素(一行为一个元素),分别这么表示: a[0] a[1] a[2], 都是 int [4]数组类型 (都是有4个int类型元素的数组类型)

我们可以这么理解: a[0] ,a[1] ,a[2] 既是二维数组的元素,又是一个数组名

a[0] 是一个数组名,有 a0 a0 a0 a0 四个元素

a[1] 是一个数组名,有 a1 a1 a1 a1 四个元素

a[2] 是一个数组名,有 a2 a2 a2 a2 四个元素

请问二维数组名a怎么理解?
    (1)代表整个数组
        sizeof(a)  typeof(a) &a 
    (2)代表数组的首地址(首个元素的地址) : &a[0]

a[0],a[1],a[2]作为数组名,怎么理解?
    (1)代表整个数组
    
    (2)代表数组的首地址(首个元素的地址) 
        a[0]    ->  &a[0][0]
        a[1]    ->  &a[1][0]
        a[2]    ->  &a[2][0]

练习:
    int a[3][4]; 
    假设二维数组a的首地址为  0x10066
    求:
        printf("%p\n",a);//0x10066
        printf("%p\n",&a);//0x10066
        printf("%p\n",&a[0]);//0x10066
        printf("%p\n",&a[0][0]);//0x10066
                        
        printf("%p\n",a+1);//0x10066 + 0x10 = 0x10076
                a在这里表示 a[0]的地址 :&a[0] ,步长为16

        printf("%p\n",&a+1);//0x10066 + 0x30 = 0x10096
                a在这里表示整个数组,&a的步长是 48

        printf("%p\n",&a[0]+1);//0x10076
                        &a[0]的步长是16

        printf("%p\n",&a[0][0]+1);//0x10066 + 4 = 0x1006a
                        &a[0][0]的步长是4
        *(a+i)  <-> *(&a[0]+i) <-> *(&a[i]) <-> a[i]

        *(*(a+i)+j) <-> *(a[i] + j)  <-> *(&a[i][0] + j) <-> *(&a[i][j]) <-> a[i][j]
    
    总结:不管是一维数组还是二维数组,元素的访问都有两种方式,下标法和指针法
        对于一维数组, a[i] <-> *(a+i)
        对于二维数组, a[i][j] <-> *(*(a+i)+j)

9,指针数组和数组指针

指针数组是一个数组,它的元素是指针类型

怎么定义?

        数据类型 * 数组名[元素个数];

        数据类型可以是任何合法类型

         比如: int * a[3];

         定义了一个数组a,有3个元素,每个元素都是 int * 类型

        sizeof(a) = 24

        double *b[5];
        定义了一个数组b,有5个元素,每个元素都是 double * 类型
        sizeof(b) = 40

    有什么用?
        保存多个地址
    例子:
        int a[9] = {1,2,3,4,5,6,7,8,9};
        int *p[3] = {&a[0],&a[3],&a[6]};
        //数组p有三个元素, p[0] ,p[1],[2],都是 int * 类型
        //p[0] 指向 a[0], p[1]指向a[3] ,p[2]指向a[6]

        printf("%d,%d,%d\n",*p[0],*(p[1]+2),*(p[2]+1));//1,6,8

        printf("%d\n",*(*(p+1)+2) );//6
            *(*(p+1)+2) <-> *(*(&p[0]+1)+2) <-> *(*(&p[1])+2)
            <-> *(p[1]+2) <-> *(&a[3]+2) <-> *(&a[5]) <-> a[5]

        printf("%d\n",p[1][2]);//同上
                p[1][2] <-> *(*(p+1)+2)
数组指针是一个指针,这个指针的基类型是一个数组(它指向某个数组)
    指针变量:就是用来保存别的对象的地址
    别的对象是 int类型,这个指针就是 int *类型 
    别的对象是 double 类型,这个指针就是 double *类型 
    ...
    别的对象是数组,这个指针就是 数组 * 类型 

    作用是什么:就是用来保存一个数组的地址

    怎么定义?
        基类型 * 指针变量名;

        基类型是数组类型,怎么表示?

    如:
        int a[4];
        数组a的类型怎么表示?
            typeof(a)
            或者
            int [4]

    ->
        typeof * p;
        或者
        //int [4] *p;//c语言标准委员会觉得这样不好看,改成下面的写法
        int (*p) [4];

    怎么保存数组地址?
        p = &a;

    数组指针保存一个一维数组的地址,应用场景有限,常用的是用数组指针保存二维数组的首地址

    例如:
        int b[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
         //二维数组有 3个元素,分别是 b[0] b[1] b[2],都是 int [4]类型 
        int (*p)[4];
        p = b;
        或者
        p = &b[0];

        printf("%d\n",p[2][3]);

    结论:
        p[2][3] <-> *(*(p+2)+3) <-> *(*(&b[0]+2)+3)
        <-> *(*(&b[2])+3) <-> *(b[2]+3) <-> *(&b[2][0]+3) <-> *(&b[2][3]) <-> b[2][3]
二维数组作为函数参数,一般也是传2个参数,第一个参数是二维数组的首地址(a[0]的地址,不是a[0][0]地址),
第二个参数是元素个数(行大小)

int array_sum(int (*p)[3] ,int n)//等价 int array_sum(int p[][3] ,int n)
{
    int i,j;
    int sum=0;
    for(i=0;i<n;i++)
    {
        for(j=0;j<3;j++)
        {
            sum += p[i][j];
        }
    }
    return sum;
}

int array_max_value(int (*p)[3] ,int n)
{
    int i,j;
    int max=p[0][0];
    for(i=0;i<n;i++)
    {
        for(j=0;j<3;j++)
        {
            if(max < p[i][j])
                max = p[i][j];
        }
    }
    return max;
}
int main()
{
    int a[4][3] = {.....};
    array_sum(a,4); //a <-> &a[0] -> 数组指针
}

练习:
    写一个函数,求二维数组的最大值
二维数组这样传参,通用性不够:如果数组的列是不同的,就没办法传了
可以进行改进:
    传三个参数,第一个参数是,二维数组第一个元素的地址(不是a[0]的地址,而是a[0][0]的地址)
        第二个、第三个参数就是行和列

    int array2_max_value(int *p ,int n,int m)
    {
        int i,j;
        int (*q)[m];//定义一个数组指针
        q = (typeof(q))p;//把 p强转赋值给q
        int max=q[0][0];
        for(i=0;i<n;i++)
        {
            for(j=0;j<m;j++)
            {
                if(max < q[i][j])
                    max = q[i][j];
            }
        }
        return max;
    }

10, const关键字

修饰普通变量,被修饰的变量的值不能修改

如: const int data = 10; //const和类型int的先后顺序无所谓

         ...

        data = 20;//错误

    const int data;//因为如果不初始化,后面又不能给它赋值,那就没有意义,建议必须初始化

修饰指针变量,有这么几种情况:
    int * const p;
    和修饰普通变量一样,表示p不能被修改,只能一直指向一个固定的对象

    int const * p;//const int * p;
    const不是修饰 p的,p是可以修改的,const的作用是指:不能通过p去修改它指向的对象
    一般会用于函数的形参,表示该函数只需要对这个指针指向的对象进行读访问,不需要进行写访问,
    加上 const就可以保护数据(避免被不小心修改)

    int const * const p;
        上面两种情况的综合,也就是说 p不能被修改,也不能通过p去修改它指向的内容

11,字符串与指针

所谓的字符串,就是一串字符,c语言中并没有字符串这个类型,那么c语言中如何表示/保存字符串呢?

1,用字符数组保存

        原理:每个元素保存字符串的一个字符

2,用字符类型的指针保存

        原理:只保存字符串的首地址(第一个字符的地址),从这个地址依次往后面找,直到遇到'\0' 从第一个字符开始到'\0'结束,这个范围里的所有字符就是完整的字符串

"" 引起来的表示一串字符串,并且最后一个字符默认为 '\0'
"" 引起来的这个表达式的值就是该字符串第一个元素的地址 
语法:
    char * 指针变量名 = "字符串";     

例如:
    char * p = "hello";
    定义了一个指针变量 p,保存了 字符串首个字符 'h'的地址 

这种形式的字符串保存在内存的一个特殊的区域, .rodata区域( read only data ),保存
在这个区域的数据只能读,不能写。
这和用字符数组保存字符串有本质的不同
例如:
    char * p = "hello";//"hello"这6个字符(包括'\0')保存在 .rodata区域,不能修改的
    *p = 'H';//错误 
    printf("%c\n",*p);//正确,打印 'h'
    *(p+1) = 'E';//错误
    printf("%c\n",*(p+1));//正确,打印 'e'

    char s[] = "hello";//"hello"这6个字符(包括'\0')保存在数组种(栈区),可读可写
    s[0] = 'H';//正确 
    printf("%c\n",s[0]);//正确 

    sizeof(p) = ? 8
    sizeof(s) = ? 6

12,几个常用的字符串处理函数(c语言标准库中的函数)

(1) strlen :用来求字符串的长度

#include <string.h> //要用字符串相关函数,需要包含这个头文件

size_t strlen(const char *s); //函数的声明,可以看出参数个数类型及返回值类型

s:要求长度的那个字符串的首地址

返回值: (size_t 就是int)

                返回字符串的长度

        char s[] = "hello";
        int len = strlen(s);

        char * p = "hello";
        len = strlen(p);

        strlen("hello");

(2) strcpy / strncpy :复制字符串,把一个字符串的内容复制到另一个字符串中
    char s1[10] = "strlen";
    char s2[10] = "hello";
    //s1 = s2;//语法错误, s1在此表示数组首地址,并且是常量,不能修改
    //s1 = "hello";//语法错误, s1在此表示数组首地址,并且是常量,不能修改

    char * q = "world";
    char * p = "hello";
    p = q;//语法没有错误,效果是 p,q都保存了 'w'的地址, "hello"字符串找不到了
    那如果想要修改数组s1的内容,应该怎么做:
        (1)一个一个元素进行赋值
            s[0] = 'h';
            s[1] = 'e';
            s[2] = 'l';
            s[3] = 'l';
            s[4] = 'o';
            s[5] = '\0';
        
        (2)调用函数 strcpy/strncpy
        #include <string.h>
        char *strcpy(char *dest, const char *src);
                dest: 目的字符串首地址
                src:  源字符串首地址
                功能就是把 src指向的字符串内容拷贝到dest指向的字符串中去
            成功返回目的字符串的首地址

            strcpy内部的拷贝流程:从 src地址开始,一个一个进行拷贝,直到遇到 '\0'结束,
            不会理会 dest是否越界

        char *strncpy(char *dest, const char *src, size_t n);
                功能是把 src指向的字符串内容至多拷贝n字节到dest指向的字符串中去
                n一般是 dest字符串的容量-1,保证拷贝的过程中不越界,最后一个字符留给'\0'

(3) strcmp / strncmp 比较两个字符串
    char * s1 = "hello";
    char * s2 = "world";
    if(s1<s2) //这是比较的两个字符串首地址的大小,和字符串的内容没有任何关系
    {
        printf("字符串s1小于s2\n");
    }
    else
    {
        printf("字符串s1大于或等于s2\n");
    }

    字符串的比较规则:从第一个字符一个一个字符进行比较,直到不相同为止,首个不相同的字符 ascii
                的大小就是两个字符串的大小

    例如:
        "123"  比 "124" 小  因为 '3' < '4'
        "123"  比  "1234" 小    因为'\0' < '4'
        "124"  比  "1234" 大    因为 '4' > '3'
    strcmp, strncmp - compare two strings

SYNOPSIS #include <string.h>

   int strcmp(const char *s1, const char *s2);
        s1,s2 就是要进行比较的两个字符串的首地址
        返回值:
            字符串s1小于s2 ,返回-1
            字符串s1等于s2 ,返回0
            字符串s1大于s2 ,返回1

        写一个函数,实现 比较两个字符串的功能

   int strncmp(const char *s1, const char *s2, size_t n);
        最多只比较两个字符串前n个元素
        写一个函数,实现 比较两个字符串前n个字节的功能
(4) 字符串连接函数  strcat/strncat
    strcat, strncat - concatenate two strings

    SYNOPSIS
    #include <string.h>

    char *strcat(char *dest, const char *src);
            dest :目标字符串首地址
            src:源字符串首地址
        返回连接之后的目标字符串首地址

        在 dest字符串的末尾连接上src字符串的内容
        char dest[20];
        char src[] = "world";

        strcat(dest,src);//在连接的时候,会先找到dest末尾(也就是 '\0'),然后再连接 src 
                        //现在 dest字符串的 '\0'在哪不确定
        printf("%s\n",dest);//不确定
        ---------------------
        char dest[20] = "hello";
        char src[5] = "world";//没有空间保存 '\0'

        strcat(dest,src);//连接 src 时,会一个一个字符进行赋值,直到 src遇到 '\0'
                        //现在 src 字符串没有 '\0' 
        printf("%s\n",dest);//不确定
        练习:
            自己实现这个函数

    char *strncat(char *dest, const char *src, size_t n);
        最多只连接 src的前n个字符到 dest中
    有一个共同的问题:避免 dest指向的内存空间足够保存连接之后的字符串
    总结: 
        所有和字符串相关的操作,都必须先考虑这个字符串是否有 '\0',如果没有'\0'会不会影响
        你接下来的操作

13,函数指针

顾名思义就是指向一个函数的指针

每个函数都有自己的地址,调用函数其实就是通过这个地址找到函数内的指令并执行

(1) 函数的地址怎么表示?
        函数名
        或者
        &函数名

(2)函数指针怎么定义?
    首先得知道基类型是什么 -》 基类型是一个函数类型
    函数类型怎么表示? -》 有两种表示方法
    2.1  typeof(函数名)

    2.2 把函数头的所有名字都去掉
            包括函数名,形参名

    例如:
    char *strcat(char *dest, const char *src);

        typeof(strcat)
        char *(char *, const char *);

    定义函数指针变量:
        typeof(strcat) * p;
        char * (*q)(char *, const char *);

练习:
    int my_strncmp(const char * s1,const char * s2,int n);

    请你定义一个指针变量并保存 my_strncmp 的地址

    typeof(my_strncmp) * p = my_strncmp;
    int (*q)(const char * ,const char * ,int) = my_strncmp;

(3) 如何通过函数指针调用函数
    语法:
        函数指针(实参列表);
        或者
        (*函数指针)(实参列表);

->
void test()
{
    typeof(my_strncmp) * p = my_strncmp;
    int (*q)(const char * ,const char * ,int) = my_strncmp;
    char * s1 = "123";
    char * s2 = "1234";
    switch (q(s1,s2,4))
    {
    case 0:
        printf("字符串s1等于s2\n");
        break;
    case -1:
        printf("字符串s1小于s2\n");
        break;
    case 1:
        printf("字符串s1大于s2\n");
        break;
    
    default:
        break;
    }
}

主要应用场景:回调函数-》不是立马调用,回头再调用/在合适的时间调用
之前写一个选择排序的函数:

void selection_sort(int a[],int n)
{
    int i,j;
    int max_index;
    int temp;
    for(i=0;i<n-1;i++)
    {
        max_index = 0;
        for(j=0;j<n-i;j++)
        {
            if(a[j]>a[max_index])
            {
                max_index = j;
            }
        }
        if(max_index != n-1-i)
        {
            temp = a[max_index];
            a[max_index] = a[n-1-i];
            a[n-1-i] = temp;
        }
    }
}

这个函数只能进行从小到大排序,如果需要从大到小排序呢?
再重新写一个函数,但是选择排序是一个功能,写两个函数太麻烦。

-》
增加一个参数,函数指针
void selection_sort(int a[],int n,int (*func)(int,int))
{
    int i,j;
    int index;
    int temp;
    for(i=0;i<n-1;i++)
    {
        index = 0;
        for(j=0;j<n-i;j++)
        {
            //不直接比大小,而是通过传进去的函数指针去调用主调函数指定的比较函数
            //如果 func这个函数指针指向的函数,第一个参数大于第二个参数返回真,就能实现升序的效果
            //如果 func这个函数指针指向的函数,第一个参数小于第二个参数返回真,就能实现降序的效果
            if(func(a[j],a[index]))
            {
                index = j;
            }
        }
        if(max_index != n-1-i)
        {
            temp = a[index];
            a[index] = a[n-1-i];
            a[n-1-i] = temp;
        }
    }
}
具体代码见   函数指针.c

14,二级指针和多级指针

int data = 10; //sizeof(data) = 4

定义一个指针变量data的地址

int *p = &data; //sizeof(p) = 8 (64位)

既然 p是一个变量,占8个字节,那么p也有地址
定义一个变量保存p的地址,肯定也是指针变量
int ** q = &p; //int * 是基类型(也就是p的类型),所以 q 是 int **类型 

//定义一个变量保存q的地址,怎么做?
//int ***r = &q;
*p <-> data
*q <-> p

**q = 200;
**q <-> *p <-> data 
分析以下代码:
void func(int x)
{
    x = 10;
}
void test()
{
    int y = 20;
    func(y);
    printf("%d\n",y);//20
}

---------------------------
void func1(char *q)
{
    static char s[] = "hello";
    q = s;
}
void test1()
{
    char * p = NULL;
    func1(p);
    //此时 p 为 NULL 
    printf("%s\n",p);//报错
}

-----------------------
总结:
    形参被改变,影响不了实参
    void swap(int *p,int *q)
    {
        int temp;
        temp = *p;
        *p = *q;
        *q = temp;
    }
    void test()
    {
        int x=10;
        int y=20;
        swap(&x,&y);
        printf("%d,%d\n",x,y);//20,10
    }
    ------------------------
    void func2(char **q)
    {
        static char s[] = "hello";
        *q = s;
    }
    void test2()
    {
        char * p = NULL;
        func2(&p);
        printf("%s\n",p);//正确打印  hello
    }

总结:
    想要在被调函数内部改变主调函数中的某个变量的值,需要把这个变量的地址传递到被调用函数。

15,动态内存分配

系统提供了几个函数用于分配动态内存,动态内存的特点:需要手动释放,如果不手动释放,会一直占用 -》 生存期 ,随内核持续性

malloc 、 calloc 、 realloc 用来动态分配内存
free 用来释放动态内存

#include <stdlib.h>//需要包含这个头文件

void *  -> 通用指针类型(不能理解为基类型为 void),它可以转换为其它类型的指针。
    void * p = 某个地址;
    int * p1 = p;
    double *p2 = p;
    char * p3 = p;
    ......
void *malloc(size_t size);
        size :你想要分配内存的字节数目
    返回值:
        失败返回 NULL (内存不足的时候会分配失败)
        成功返回 这块内存的首地址

        malloc(20);//操作系统为该程序分配20个字节,没有名字,怎么访问呢?
                    //只能通过它的首地址来访问,所以该函数才需要返回这个首地址
        为什么返回 void * ,而不是 int * / double * / char *  ......
        因为它不知道你要用这块内存保存什么类型的数据,
            如果返回 int *,结果你是用来保存double类型的数据呢?还需要强转
            如果返回 double *,结果你是用来保存char 类型的数据呢?还需要强转
            ...
        所以干脆不指定具体的类型,用 void *这种通用类型来表示,你可以根据实际情况进行赋值/转换

    例如:
        我想要动态分配4个字节用来保存一个整数
        int * p = (int *)malloc(4); //int * p = malloc(4);
        //p指向了这四个字节, 对四个字节的访问可以通过 *p 进行

        *p = 100;
        printf("%d\n",*p);
void free(void *ptr); //释放 动态分配的内存
        ptr :动态分配内存的首地址
void *calloc(size_t nmemb, size_t size);
    作用和 malloc类似,分配一个数组, nmemb * size 为要分配的总字节数目
    nmemb :数组元素个数
    size :单个元素所占字节大小
    返回值:
        失败返回 NULL (内存不足的时候会分配失败)
        成功返回 这块内存的首地址
    分配的这块内存,全部初始化为0

void *realloc(void *ptr, size_t size);
    是用来为已经分配的动态内存进行扩容的 
    ptr :原来分配的内存的首地址
    size :是扩容之后的大小
    返回值:
        失败返回 NULL (内存不足的时候会分配失败)
        成功返回 扩容之后的内存的首地址

    扩容之后,原来的数据保留

16, main函数的参数问题

主函数是可以有参数的,在运行程序时由“终端”给出实参,实参的类型都是字符串

形参怎么写,有固定的格式

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

第一个形参 argc: 表示传入的实参个数(运行程序时终端输入的字符串个数,以空格分隔)
第二个形参 argv: 指针数组,有 argc个元素

比如:
    运行时输入  ./a.exe 123 abc 
    调用主函数,进行传参:
    int argc = 3;
    char *argv[] = {"./a.exe","123","abc"};

    ->
    argv[0] = "./a.exe";
    argv[1] = "123";
    argv[2] = "abc";

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值