由浅入深地学习指针(学习指针必看)

news2024/11/15 22:31:45

目录

指针初阶

指针定义

指针和指针类型

c语言的整型指针解引用与整型变量的区别

内存

指针和指针类型

指针类型的意义

野指针

规避野指针

指针运算

指针和数组

二级指针

指针数组

指针进阶

指针的概念再提起

字符指针

《剑指offer》

字符串常量:

指针数组

数组指针

数组名是数组首元素地址

数组传参和指针传参

一维数组传参

二维数组传参

一级指针传参

二级指针传参

加深印象

函数指针

有趣的代码

推荐书籍《C陷阱与缺陷》

函数指针数组

模拟计算器

指向函数指针数组的指针

回调函数

冒泡排序

指针和数组结合的冒泡排序

模仿qsort实现冒泡排序通用算法

快速排序改良


指针初阶内容

指针定义

指针从根本上来看是一个值为内存地址的变量或者数据对象,利用地址,他的值直接指向(points to)存在电脑存储器中另一个地方的值。指针变量的值是地址。例如一个指针的变量名为p,那么我们可以通过 p = &a;将a的地址赋值给p,从而我们可以通过对p进行操作,对a进行修改等操作。地址形象地称为指针。

编号 - 地址 - 指针,they are same

要创建指针变量,首先要先生命指针变量的类型。例如:

int *a = NULL;//指针的类型是int *,指针所指向的是int类型的.
short *ps = NULL;
long *pc = NULL;
char *b = NULL;//指针的类型是char *,指针所指向的是char类型的.
double *c = NULL;//指针的类型是double *,指针所指向的是double类型的.
int **ptr;//指针的类型是int **,指针所指向的是int*类型的.
#include<stdio.h>
int main()
{
    int a = 10;//a占4个字节;
    int * pa = &a;//拿到的是a的4个字节中第一个字节的地址;
    *pa = 20;
    return 0;
}

指针是用来存放地址的,地址是唯一标示一块地址空间的。

指针的大小在32位平台是4个字节,在6位平台是8个字节。

指针和指针类型

#include<stdio.h>
int main()
{
    int* pa;
    char* pc;
    float * pf;
    printf("%d\n",sizeof(pa));
    printf("%d\n",sizeof(pc));
    printf("%d\n",sizeof(pf));
    return 0;
}
8
8
8

        指针类型决定了指针解引用的权限有多大

        引入:简单介绍一下整型变量,指针,引用符和解引用符

        定义一个整型变量a: int a;

        如果使用引用符号&,&a则是整型变量a的地址,也就是一个整型指针。

        定义一个整型指针b: int *b;b=&a;(这里把变量a的地址,也就是指针&a赋给了指针b)

        如果对一个指针使用解引用符号*,如b,便表示整型指针b存储的地址所指向的整型变量。

指针类型的意义

#include<stdio.h>
int main()
{
    int a = 0x11223344;
    char *pc = &a;
    //0 1 2 3 4 5 6 7 8 9 a b c d e f
    //11111111
    //    8421
    //int *pa = &a;
    //*pa = 0;
    //printf("%d\n",*pa);
    return 0;
}

c语言的整型指针解引用与整型变量的区别

#include<stdio.h>
int main()
{
    int a = 0;
    a++;
    printf("%d\n",a);
    int *b;
    b = &a;
    a=0;
    *b++;
    printf("%d\n",a);
    return 0;
}
输出:1
     0

        同样开始的时候赋予变量0值,直接对变量本身自增成功了。

        但是拿变量的指针的解引用来自增,结果变量并没有改变。

#include<stdio.h>
int main()
{
    int a;
    int *b;
    b = &a;
    a=0;
    (*b)++;
    printf("%d\n",a);
    return 0;
}
1

把代码中的指针的整型指针的解引用加上括号后再自增,结果自增成功了。

经过更多的实验,得出结论:内存分布

C中,,和++运算符同时存在时,后者比前者优先级更高,电脑会把*b++解读成 *(b++),即把b存的地址+1,再解应用,实际上解引用得到的不是本来指针所指的整型变量。

总结:在使用整型变量的指针时,(其实不止是对整型变量),要注意运算符的优先级。解引用时要注意解引用的指针是否发生了变化。

内存

内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的。 所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。 为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址。

0
1
2
3
4

                给每个格子编了号

                32位 - 32根地址线 - 物理线 - 通电 - 1/0

                64位 - 64根地址线 - 物理线 - 通电 - 1/0

                电信号转换成数字信号:0和1组成的二进制序列

                00000000000000000000000000000000---内存编号

                11111111111111111111111111111111---内存编号

                2^32个编号

                假设:一个内存单元是1 bit

                2^32 bit = 512 MB =0.5 GB

#include<stdio.h>
int main()
{
    int a = 10;//a在内存重要分配的空间 - 4个字节
    printf("%p\n",&a);
    int* pa = &a;//pa是用来存放地址的,在C语言中是指针变量
    //* 说明 pa是指针变量
    //int 说明pa执行的对象是int类型的
    printf("%p\n",pa);
    char ch = 'w';
    printf("%p\n",ch);
    char* pc = &ch;
    printf("%p\n",*pc);
    return 0;
}
000000000061FE0C
000000000061FE0C
0000000000000077
0000000000000077

        就可以输出a的内存地址,比如说我的a的地址就是000000000061FE1C(一串十六进制数)

int main()
{
    int a = 10;
    int* pa = &a;
    *pa = 20;//* 叫做解引用操作 *pa就是通过pa里边的地址,找到a
    //就是用*pa赋一个值改变原来的值
    printf("%d\n",a);
    return 0;
}
输出结果:20
int main()
{
    printf("%d\n",sizeof(char*));
    printf("%d\n",sizeof(short*));
    printf("%d\n",sizeof(int*));
    printf("%d\n",sizeof(long*));
    printf("%d\n",sizeof(long long*));
    return 0;
}
输出:
8
8
8
8
8

指针和指针类型

#include<stdio.h>
int main()
{
    int a = 0x11223344;
    int*pa = &a;
    printf("%p\n",a);
    printf("%p",*pa);
    *pa = 0;
    printf("%p",a);
    printf("%p",*pa);
    return 0;
}
0000000011223344
0000000011223344
0000000000000000
0000000000000000

                char* pc 不能来复制 int型 的地址

#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int *p = arr;
    //char* pc = arr;//error,此处报错
    printf("%p\n",arr);
    printf("%p\n",p);
    printf("%p\n",p+1);
    //printf("%p\n",*pc);
    return 0;
}
#include<stdio.h>
int main()
{
    int arr[10] = {0};
    char ch[10] = {'i'};
    int *p = arr;
    char* pc = ch;
    printf("%p\n",arr);
    printf("%p\n",p);
    printf("%p\n",p+1);
    printf("%p\n",ch);
    printf("%p\n",pc);
    printf("%p",pc+1);
    return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDD6
000000000061FDD6
000000000061FDD7

指针类型的意义

        1.指针类型决定:指针解引用的权限有多大

        2.指针类型决定:指针走一步,能走多长(步长)

        字符型指针加一,指针加一

        整型指针加一,指针加四

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int *p = arr;
    for(int i = 0;i < 10; i++)
    {
        *(p+i) = 1;//p+i 其实是下标为i的地址//而char类型则是一个字节一个字节访问
    }
    return 0;
}

野指针

        概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

        1.解释:指针指向的位置不可知

        2.三个成因:

         指针未初始化 ​           指针越界访问 ​           指针指向的空间释放(动态内存开辟)

        1.指针未初始化

#include<stdio.h>
int main()
{
    //这里的p就是一个野指针
    int *p;//p是一个局部的指针变量,局部变量不初始化的话,默认是随机值
    *p = 20;//非法访问内存
    return 0;
}

        2.指针越界

#include<stdio.h>
int main()
{
    int arr[10] = {0};
    int* p = arr;
    int i = 0;
    for(i = 0;i <= 10; i++)
    {
        *p = i;
        p++;//当i= 10,时,造成的第十一位出现,造成越界
    }
    return 0;
}

        3.指针指向的空间释放(动态内存开辟)

#include<stdio.h>
int* test()
{
    int a = 10;
    return &a;//使用后生命周期结束,归还操作系统
}
int main()
{
    int* p = test();
    *p = 20;//非法访问内存
    return 0;
}

        局部变量a出函数后销毁,本属于a的空间不是当前程序的,已经还给操作系统,可能会被分配储存其他内容

        访问一块已经被释放的空间,则会产生野指针

        像这种访问临时变量的地址,都存在问题

        除非出了范围不销毁,比如初始 static 变量

规避野指针

 

        1.指针初始化

        2.小心指针越界

        3.指针指向空间释放,及时置NULL

        4.指针使用之前检查有效性(NULL不能访问)

#include<stdio.h>
int main()
{
    //当前不知道p应该初始化为何地址的时候,直接初始化为NULL
    int* p = NULL;
    //明确知道初始化的值
    int a = 10;
    int* ptr = &a;
    //C语言本身是不会检查数据的越界行为的。
    int* p1 = NULL;
    if(p != NULL)
        *p1 = 10;
    return 0;
}

指针运算

        ·指针+-整数 ·指针-指针 ·指针的关系运算

 1.指针+-整数

#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[0];vp < &values[N_VALUES];)
    {
        *vp++ = 0;
    }//成功循环一遍
    return 0;
}

        相应的,可以倒着进行循环。

#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[N_VALUES - 1];vp >= &values[0];)
    {
        *--vp = 0;
    }
    return 0;
}
#include<stdio.h>
#define N_VALUES 5
int main()
{
    float values[N_VALUES];
    float *vp;
    //指针+-整数:指针的关系运算
    for(vp = &values[N_VALUES - 1];vp >= &values[0];vp--)
    {
        *vp = 0;
    }
    return 0;
}

        实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。

2.指针-指针

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    printf("%d\n",&arr[9] - &arr[0]);//指针-指针得到的是两个指针之间的元素个数
    return 0;
}
9
#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    char c[5];
    printf("%d\n",&arr[9] - &c[0]);//error
    return 0;
}
        //虽然可能有值算出,但是因为指针类型不同,所以是无效的

        指针和指针相减的前提是两个指针指向同一块空间。

3.指针的关系运算

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

4.strlen函数再回顾

4.1.库函数法

#include<stdio.h>
#include<string.h>
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    //比如说['a']['b']['c']['d']
    printf("%d\n",strlen(arr));
    return 0;
}//直接调用库函数

4.2.函数计数器法

#include<stdio.h>
#include<string.h>
int my_strlen(char* str)
{
    int count = 0;
    while(*str != '\0')
    {
        count++;
        str++;
    }
    return count;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}//计数器方法

4.3.递归法

#include<stdio.h>
int my_strlen(char* str)
{
    if(*str != '\0')
    {
        return 1 + my_strlen(str + 1 );
    }
    else return 0;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}//递归算法

4.4.指针-指针法

#include<stdio.h>
int my_strlen(char* str)
{
    char* start = str;
    while(*str != '\0')
    {
        str++;
    }
    return str - start;
}
int main()
{
    char arr[] = {};
    scanf("%s",&arr);
    printf("%d\n",my_strlen(arr));
    return 0;
}

指针和数组

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    printf("%p\n",arr);//数组名是数组首元素的首地址
    printf("%p\n",&arr[0]);
    printf("%p",&arr[1]);
    return 0;
}
000000000061FDF0
000000000061FDF0
000000000061FDF4

                数组遍历

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    for(int i = 0;i < 10; i++)
    {
        printf("%p <==> %p\n",&arr[i],p + i);
    }
    return 0;
}
000000000061FDE0 <==> 000000000061FDE0
000000000061FDE4 <==> 000000000061FDE4
000000000061FDE8 <==> 000000000061FDE8
000000000061FDEC <==> 000000000061FDEC
000000000061FDF0 <==> 000000000061FDF0
000000000061FDF4 <==> 000000000061FDF4
000000000061FDF8 <==> 000000000061FDF8
000000000061FDFC <==> 000000000061FDFC
000000000061FE00 <==> 000000000061FE00
000000000061FE04 <==> 000000000061FE04
#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p = arr;
    for(int i = 0;i < 10; i++)
    {
        *(p + i) = i;
    }
    for(int i = 0;i < 10; i++)
    {
        printf("%d ",*(p+i));
    }
    return 0;
}
0 1 2 3 4 5 6 7 8 9

                当然也可以通过如下方式进行操作

#include<stdio.h>
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;//数组名
    printf("%d\n",arr[2]);
    printf("%d\n",p[2]);//p[2]-->*(p + 2)
    printf("%d\n",*(p+2));
    printf("%d\n",2[arr]);//其效果实际上就是arr[2]-->*(arr + 2)
    return 0;
}
3
3
3
3

二级指针

#include<stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;//pa是指针变量,一级指针
    //ppa就是一个二级指针变量
    int** ppa = &pa;//pa也是个变量,&pa取出pa在内存中的起始地址
    int*** pppa = &ppa;
    printf("%p\n",a);
    printf("%p\n",*pa);
    printf("%p\n",**ppa);
    printf("%p\n",***pppa);
    return 0;
}
000000000000000A
000000000000000A
000000000000000A
000000000000000A

指针数组

        指针数组本质上是数组

#include<stdio.h>
int main()
{
    int arr[10];//整形数组 - 存放整型的数组就是整型数组
    char ch[5];//字符数组 - 存放字符的数组就是字符数组
    //指针数组 - 存放指针的数组
    int* parr[5];//整型指针的数组
    char* pch[5];//字符指针的数组
    return 0;
}

代码三种境界:

1.看代码就是代码

2.看代码就是内存

3.看代码还是代码

指针进阶

指针的概念再提起

1.指针就是一个变量,用来存放地址,地址唯一标识一块内存空间。

2.指针的大小是固定的4/8个字节(32位平台/64位平台)。

3.指针是有类型的,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。

4.指针的运算。

指针:通过地址能找到所需的变量单元,因此说,地址指向该变量单元,将地址形象化的称为指针;可理解为地址就是指针; ​ 指针变量:存放指针(地址)的变量; ​ 指针的作用:通过指针间接访问内存; ​ 指针变量的定义:类型名 * 指针变量名 ​ 一个变量的指针的含义包含两个方面,一是存储单元的纯地址,二是指向存储单元的数据类型,故定义指针变量时要指定基类型,用来指定此指针变量可以指向的变量的类型; 注释:* 取内容运算符,间接访问运算符 ,*p代表指针变量p所指向的对象;& 取地址运算符,&a是变量a的地址;

# include <stdio.h>
int main()
{
    int a;
    a = 10;
    int *p;
    p = &a;
    printf("a address is %p\n",p);//这里得到的地址是0x0060ff00,不同编译器得到的地址不一样
    int *p2 = (int *)0x0060ff00;//将指针变量p2指向这块内存地址
    *p2 =20;
    printf("在内存的%p的位置,存放值是%d\n",p2,*p2);
}//打印输出:在内存的0x0060ff00的位置,存放值是20

字符指针

#include <stdio.h>
int main()
{
    char ch = 'q';
    char* pc = &ch;
    *pc = 'p';
    printf("%c",ch);
    return 0;
}
输出结果:h

或者用下面的方式

#include<stdio.h>
int main()
{
    char* ps = "hello bit.";
    char arr[] = "hello bit.";
    printf("%s\n", ps);
    printf("%s\n", arr);
    printf("%p\n",ps);
    printf("%p\n",arr);
    return 0;
}
输出结果:hello bit.
        hello bit.
        0000000000404000
        000000000061FE0D//地址实际上不相同的

1.习题-判断字符串是否相等

#include<stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char* str3 = "hello bit.";
    char* str4 = "hello bit.";
    if (str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf ("str1 and str2 are not same\n");
    if(str3 == str4)
        printf ("str3 and str4 are same\n");
    else
        printf ("str3 and str4 are not same\n");
    return 0;
}
输出结果:str1 and str2 are not same
        str3 and str4 are same

题目解释:str1[]、str2[]是两个数组,地址是不一样的;

str3、str4是两个指针变量,两个里面放的都是同一个h(常量字符串不能改,所以两者共用一份字符 串,所以是一样的)

《剑指offer》

#include<stdio.h>
int main()
{
    char*ps = "hello bit.";
    char arr[] = "hello bit.";
    printf("%s\n", ps);
    printf("%s\n", arr);
    return 0;
}
代码无法输出 err

常量字符串是不能改的

#include<stdio.h>
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char* str3 = "hello bit.";
    const char* str4 = "hello bit.";
    if(str1 == str2)
        printf("str1 and str2 are same\n");
    else
        printf ("str1 and str2 are not same\n");
    if(str3 == str4)
        printf ("str3 and str4 are same\n");
    else
        printf ("str3 and str4 are not same\n");
    return 0;
}

const char常量字符串

字符串常量

字符常量: 由一对单引号括起来的单个字符,如'a', 'B';

字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”;

字符串常量: 由一对双引号括起来的多个字符的序列,如"a",“I Love u”

字符串常量是不能改的,所以只需要存储一份即可。

指针数组

#include<stdio.h>
int main()
{
    //指针数组
    //数组 - 数组中存放的是指针(地址)
    int* arr[4];
    int a = 10;
    int b = 20;
    int c = 30;
    int* arr[3] = { &a, &b, &c };//没有应用场景的
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        printf("%d\n",*arr[i]);
    }
    return 0;
}
#include<stdio.h>
int main()
{
    int a[5] = { 1,2,3,4,5 };
    int b[] = { 2,3,4,5,6 };
    int c[] = { 3,4,5,6,7 };
    
    int* arr[3] = {a,b,c};//arr[]数组又涵盖了上面三个数组;把a,b,c三个数组的首元素地址放入arr[]中去了。
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        int j = 0;
        for(j = 0;j < 5;j++)
        {
            printf("%d ",*(arr[i] + j));
        }
        printf("\n");
    }
    return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7
#include<stdio.h>
int main()
{
    int a[5] = { 1,2,3,4,5 };
    int b[] = { 2,3,4,5,6 };
    int c[] = { 3,4,5,6,7 };
    
    int* arr[3] = {a,b,c};
    int i = 0;
    for(i = 0;i < 3; i++)
    {
        int j = 0;
        for(j = 0;j < 5;j++)
        {
            printf("%d ",(arr[i][j]));
        }
        printf("\n");
    }
    return 0;
}
1 2 3 4 5
2 3 4 5 6
3 4 5 6 7

arr[i] [j]和arr[i]+j等价,模拟了二维数组,但不是真正的二维数组,因为二维数组是连续的

arr
int* 1 2 3 4 5 ——a
int* 2 3 4 5 6 ——b
int* 3 4 5 6 7 ——c

数组指针

数组指针是一种指针。

#include<stdio.h>
int main()
{
    int a = 10;
    int* pa = &a;//整型指针
    char ch = 'w';
    char* pc = &ch;//字符指针
    
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    //arr - 数组名是首元素的地址 - arr[0]的地址
    int (*parr)[10] = &arr;//取出整个数组的地址
    //parr就是一个数组指针
    
    double* d[5];
    double* (*pd)[5] = &d;//pd就是一个数组指针
    return 0;
}

1.指针数组和数组指针之间的区别

#include<stdio.h>
int main()
{
    int arr[10] = { 0 };
    int* p1 = arr;
    int (*p2)[10] = &arr;
    printf("%p\n",arr);
    printf("%p\n",&arr);//两者指向同一个地址,但是两者类型不同
    
    printf("%p\n",p1);
    printf("%p\n",p1+1);
    printf("%p\n",p2);
    printf("%p\n",p2+1);
    return 0;
}
000000000061FDE0
000000000061FDE0
000000000061FDE0
000000000061FDE4
000000000061FDE0
000000000061FE08

类似与下面的例子:

char c = 'a';//97
int c = 97;//两者值是一样的,但是类型不同

数组名是数组首元素地址

但是有两个例外

1. sizeof(数组名) - 数组名表示整个数组计算的是整个数组的大小单位是字节

2.&数组名 - 数组名 表示整个数组取出来的整个数组的地址

2.一维数组的数组指针应用

#include<stdio.h>
int main()
{
    int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };//实际上一维数组不适合取数组指针
    //int* pa = arr;
    //for(int i = 0;i < 10; i++)
    //{
        //printf("%d ",*pa+i);
    //}
    int (*pa)[10] = &arr;
    for(int i = 0;i < 10; i++)
    {
        printf("%d ",*((*pa) + i));//这样写实际上是非常别扭的
    }
    return 0;
}

3.二维数组的数组指针应用

原先的写法:

#include<stdio.h>
void print(int arr[3][5], 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 arr[3][5] = {{1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7}};
    print(arr, 3, 5);
    return 0;
}

数组指针写法:

#include<stdio.h>
void print(int(*p)[5], int r, int c)
{
    int i = 0;
    int j = 0;
    for(i = 0;i < r; i++)
    {
        for(j = 0;j < c; j++)
        {
            printf("%d ",*(*(p+i) + j));
        }
        printf("\n");
    }
}
​
int main()
{                       arr[0]      arr[1]      arr[2]
    int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
    print(arr, 3, 5);//arr数组名,传参数组的首元素地址
    return 0;
}
//二维数组的数组名表示首元素的地址
//二维数组的首元素是:第一行
int arr[10];//整型数组
int *parr1[10];//整型指针的数组
int (*parr2)[10];//整型数组指针,该指针能够指向一个数组,数组10个元素,每个元素的类型是int
int (*parr3[10])[5];//整型数组指针的数组 parr3是一个存放数组指针的数组 该数组能够存放10个数组指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

数组传参和指针传参

1.一维数组传参

#include<stdio.h>
void test(int arr[])//对的
{}
void test(int arr[10])//对的
{}
void test(int *arr)//对的
{}
void test2(int *arr[20])//对的
{}
void test2(int **arr)//对的
{}
int main()
{
    int arr[10] = {0};
    int *arr2[20] = {0};//存放int*的数组
    test(arr);
    test2(arr2);
    return 0;
}

2.二维数组传参

#include<stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][5])
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字
//因为对一个二维数组而言,可以不知道有多少行,但必须知道一行有几个元素
//这样才方便运算
void test(int (*parr)[5])
{}
int main()
{
    int arr[3][5];
    test(arr);
    return 0;
}

3.一级指针传参

#include<stdio.h>
void print(int* ptr,int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",*(ptr + i));
    }
}
​
void test(char* p)
{
    printf("%c",*p);
}
​
int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    //p是一级指针
    print(p,sz);
    char ch = 'w';
    char* pch = &ch;
    test(&ch);
    test(pch);
    return 0;
}

4.二级指针传参

#include<stdio.h>
void test(int** ptr)
{
    **ptr = 20;
}
int main()
{
    int a = 10;
    int* pa = &a;//pa是一级指针
    int** ppa = &pa;//ppa是二级指针
    //把二级指针进行传参
    test(ppa);
    test(&pa);//传一级指针变量的地址
    int* arr[10] = {0};
    test(arr);//传存放一级指针的数组
    printf("%d\n",a);//输出20,
    return 0;
}
20

加深印象

一级指针:int* p;——整型指针,指向整型的指针 char* pc;——字符指针,指向字符的指针 void* pv;—— 无类型指针

二级指针:char** p; int** p;

数组指针:指向数组的指针 —— int(*p)[4];

数组:

一维数组 二维数组 指针数组——存放指针的数组

函数指针

函数指针:指向函数的指针,存放函数地址的指针

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int main()
{
    int a = 10;
    int* pa = &a;
    char ch = 'w';
    char* pc = &ch;
    int arr[10] = {0};
    int (*parr)[10] = &arr;//取出数组的地址
    //parr是指向数组的指针 - 存放的是数组的地址
    
    //函数指针 - 存放函数地址的指针
    //&函数名 - 取到的就是函数的地址
    //pf就是一个函数指针变量
    int (*pf)(int,int) = &Add;
    printf("%p",&Add);
    printf("%p",Add);//两者相等
    return 0;
}
0000000000401550
0000000000401550

数组名 != &数组名,这两者不等价。数组名是数组首元素的地址,&数组名则是数组所有元素的地址。

函数名 == &函数名,这两者都是函数的地址名

#include<stdio.h>
void test(char* str)
{}
int main()
{
    void(*pt)(char*) = &test;
    return 0;
}

函数指针传参

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int main()
{
    //函数指针 - 存放函数地址的指针
    //&函数名 - 取到的就是函数的地址
    //pf就是一个函数指针变量
    int (*pf)(int,int) = &Add;//int (*pf)(int,int) = Add;这样也是可以的
    //也就是说Add等价于pf
    //int ret = pf(3,5);等价于int ret = Add(3,5);这么写也是对的
    int ret = (*pf)(3,5);
    //int ret = *pf(3,5);这样是不行的
    printf("%d\n",ret);
    return 0;
}
8//说明程序正确
int ret = pf(3,5);
​
int ret = Add(3,5);
​
int ret = (*pf)(3,5);

以上三种写法都是等价的,正确的

int ret = *pf(3,5);这个是例外,是错误的

有趣的代码

//代码2
((*(void)(*)())0)();
//void(*)() - 函数指针类型
//(void(*)())0 - 对0进行强制类型转换,被解释为一个函数地址
//*(void(*)())0 - 对0地址处进行解引用操作
//(*(void(*)())0)() - 调用0地址处函数
//代码2
void (*signal(int ,void(*)(int)))(int);
//函数指针类型嵌套了一个函数指针类型
//signal和()先结合,说明signal是函数名
//signal函数的第一个参数类型是int,第二个参数类型是函数指针
//该函数指针,指向一个参数为int,返回类型是void的函数
//signal是一个函数的声明

代码1: 0前面的是一个函数指针,0被强制转换成函数指针类型。实质是调用0地址处放的函数。该函数无参,返回类型是void。

代码2:可以理解为void (*) (int) signal(int ,void ( *)(int));//但是代码不允许这么写,但是可以这么理解

typedef —— 对类型进行重定义、重命名

typedef void(* pfun_t)(int); //对void(*)(int)的函数指针类型重命名为pfun_t

//typedef unsigned int uint;

pfun_t signal(int, pfun_t);

推荐书籍《C陷阱与缺陷》

函数指针数组

整型指针数组 int* arr[5];

函数指针数组

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int Sub(int x,int y)
{
    return x - y;
}
int main()
{
    int (*pf1)(int,int) = Add;
    int (*pf2)(int,int) = Sub;//返回类型参数是一样的
    int (*pfArr[2])(int,int) = {Add, Sub};//pfArr就是函数指针数组
    return 0;
}

1.模拟计算器

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int Sub(int x,int y)
{
    return x - y;
}
int Mul(int x,int y)
{
    return x * y;
}
int Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {
        menu();
        int x;
        int y;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
            case 1: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Add(x,y);
                break;
            case 2: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Sub(x,y);
                break;
            case 3: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Mul(x,y);
                break;
            case 4: 
                printf("请输入两个操作数:>\n");
                scanf("%d %d",&x,&y);
                printf("ret = %d\n",ret);
                ret = Div(x,y);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误,重新选择");
                break;
        }
        
    }while(input);
    int (*pfArr[2])(int,int) = {Add, Sub};//pfArr就是函数指针数组
    return 0;
}

这时候可以用函数指针来进行模拟计算器

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int Sub(int x,int y)
{
    return x - y;
}
int Mul(int x,int y)
{
    return x * y;
}
int Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {     
        menu();
        //pfArr就是函数指针数组
        //转移表 - 《C和指针》
        int (*pfArr[5])(int ,int) = {NULL,Add,Sub,Mul,Div};
        int x = 0;
        int y = 0;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        if(input >= 1&&input <= 4)
        {
            printf("请输入两个操作数:>\n");
            scanf("%d %d",&x,&y);
            ret = (pfArr[input])(x,y);
            printf("ret = %d",ret);
        }
        else if(input == 0)
        {
            printf("退出程序\n");
            break;
        }
        else
        {
            printf("选择错误,请重新选择\n");
        }
    }while(input);
    return 0;
}

指向函数指针数组的指针

函数指针数组本质是数组,取出的是函数指针数组的地址。

整型指针的数组

int* arr[5];
int* (*p2)[5] = &arr;
//p2是指向整型指针数组的指针
函数指针数组 &函数指针数组

int(*p)(int,int);//函数指针
int(* p2[4])(int,int);//函数指针数组
int(* (*p3)[4])(int,int) = &p2;//取出的是函数指针数组的地址
//p3就是一个指向函数指针数组的指针

理解以下的代码

void test(const char* str)
{
    printf("%s\n",str);
}
int main()
{
    //函数指针pfun
    void (*pfun)(const char*) = test;
    //函数指针的数组pfunArr
    void (*pfunArr[5])(const char* str);
    pfunArr[0] = test;
    //指向函数指针数组pfunArr的指针ppfunArr
    void (*(*ppfunArr)[10])(const char*) = &pfunArr;
    return 0;
}

以整型数组来类比理解

#include<stdio.h>
int main()
{
    int arr[10];
    //数组元素类型 - int
    //arr数组的类型是 int[10]
    return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

1.冒泡排序

首先回顾一下之前的知识,我们熟悉的冒泡排序

#include<stdio.h>
void bubble_sort(int arr[],int sz)
{
    int t = 0;
    for(int i = 0;i < sz - 1; i++)
    {
        for(int j = 0;j < sz - 1 - i; j++)
        {
            if(arr[j] > arr[j+1])
            {
                t = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = t;
            }
        }
    }
}
void print_arr(int arr[],int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
int main()
{
    int arr[10] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    print_arr(arr,sz);
    bubble_sort(arr,sz);
    print_arr(arr,sz);
    return 0;
}

2.指针和数组结合的冒泡排序

#include<stdio.h>
#define N 100
int inputarr (int a[]);
void bubble_sort(int *arr, int length);
void outputarr (int *p,int n);
void swap(int *a,int *b);
​
int main()
{
    int a[N];
    int n=inputarr(a);
    bubble_sort(a, n); 
​
    outputarr (a, n);
    return 0;
}
int inputarr (int a[])
{   
    int n;
    int i = 0; 
​
    while(scanf("%d",&a[i]))
    {
        if(a[i]==0)
            break;
        i++;
    }
    n = i;
    return n;
​
}
void bubble_sort(int *arr, int n)
{    for(int i=0;i<n-1;i++)
    {
        for(int j=0;j<n-1-i;j++){
    
        if(arr[j]>arr[j+1])
        {
            swap(&arr[j+1],&arr[j]);
        }
     } 
​
                
    }
}         
​
void outputarr (int a[],int n)
{   int i;
    for(i=0;i<n;i++){
    printf("%d ",a[i]);
    }
​
}
void swap(int *a,int *b)
{
     int temp;
     temp=*a;
     *a=*b;
     *b=temp; 
}

3.模仿qsort实现冒泡排序通用算法

#include<stdio.h>
void bubble_sort(void* base,int sz,int width,int(*cmp)(const void*e1,const void*e2))
{
    for(int i = 0;i < sz - 1; i++)
    {
        for(int j = 0;j < sz - 1 - i; j++)
        {
            if(cmp((char*)base+j*width,(char*)base+(j+1)*width)>0)
            {
                //交换
                Swap((char*)base+j*width,(char*)base+(j+1)*width,width);
            }
        }
    }
}
​
void Swap(char* buf1,char* buf2,int width)
{
    for(int i = 0;i < width; i++)
    {
        char tmp = *buf1;
        *buf1 = *buf2;
        *buf2 = tmp;
        buf1++;
        buf2++;
    }
}
​
void print_arr(arr[],sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
​
int cmp_int(const void * p1,const void * p2)
{
    return (*(int *)p1 - *(int *)p2);//强制转换
}
​
void test()
{
    int arr[] = { 1,3,5,7,9,2,4,6,8,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    bubble_sort(arr,sz,sizeof(arr[0]),cmp_int);
    print_arr(arr,sz);
}
​
int main()
{
    test();
    return 0;
}

(char* )base + j * width//强制转换类型成char类型,从而指定相应的位置

之所以用char,原因是char类型占字节较小,比较好判断

4.快速排序改良

qsort函数适用于整型数据、字符串数据、结构体数据。而上面的冒泡函数只能排序规定好的数据。

演示一下qsort函数的使用:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
//qsort函数的使用者得实现一个比较函数
​
//void qsort(void* base,size_t num,size_t size,int (*compar)(const void*,const void*))
​
//base中存放的是待排序数据中第一个对象的地址
//size_t num元素的个数
//size_t size一个元素的大小
//int (*compar)(const void*,const void*)用来比较两待排序数据中的2个元素的函数
void printf_arr(int arr[],int sz)
{
    for(int i = 0;i < sz; i++)
    {
        printf("%d ",arr[i]);
    }
    printf("\n");
}
​
int cmp_int(const void * p1,const void * p2)
{
    return (*(int *)p1 - *(int *)p2);//强制转换
}
​
void test1()
{
    //整型数据的排序
    int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
    int sz = sizeof(arr) / sizeof(arr[0]);
    //排序
    qsort(arr,sz,sizeof(arr[0]),cmp_int);
    //打印
    print_arr(arr,sz);
}
​
struct Stu
{
    char name[20];
    int age;
};
​
int sort_by_age(const void* e1,const void* e2)
{
    return ((struct Stu*)e1) ->age - ((struct Stu*)e2) ->age;//升序   降序只要把e1 e2位置交换
}
​
int sort_by_name(const void* e1,const void* e2)
{
    return strcmp(((struct Stu*)e1)->name , ((struct Stu*)e2)->name);//strcmp是比较内容的
}
​
void test2()
{
    //使用qsort函数排序结构体数据
    struct Stu s[] = {{"zhangsan",30},{"lisi",34},{"wangwu",20}};
    int sz = sizeof(s) / sizeof(s[0]);
    //按照年龄来排序
    qsort(s,sz,sizeof(s[0]),sort_by_age);
    //按照名字来排序
    qsort(s,sz,sizeof(s[0]),sort_by_name);
}
int main()
{
    //test1();
    test2();
    return 0;
}

A函数 B函数(A函数的地址)(B函数形参部分是A函数指针)

5.改造计算器

#include<stdio.h>
int Add(int x,int y)
{
    return x + y;
}
int Sub(int x,int y)
{
    return x - y;
}
int Mul(int x,int y)
{
    return x * y;
}
int Div(int x,int y)
{
    return x / y;
}
void menu()
{
    printf("*******************************\n");
    printf("*********1.add     2.sub*******\n");
    printf("*********3.mul     4.div*******\n");
    printf("**************0.exit***********\n");
    printf("*******************************\n");
}
​
void Calc(int(*pf)(int,int))
{
    int x = 0;
    int y = 0;
    printf("请输入两个操作数:>\n");
    scanf("%d %d",&x,&y);
    return pf(x,y);
}
int main()
{
    int input = 0;
    //计算器-计算整型变量的加减乘除
    do
    {
        menu();
        int x;
        int y;
        int ret = 0;
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
            case 1: 
                ret = Calc(Add);
                printf("ret = %d\n",ret);
                break;
            case 2: 
                ret = Calc(Sub);
                printf("ret = %d\n",ret);
                break;
            case 3: 
                ret = Calc(Mul);
                printf("ret = %d\n",ret);
                break;
            case 4: 
                ret = Calc(Div);
                printf("ret = %d\n",ret);
                break;
            case 0:
                printf("退出程序\n");
                break;
            default:
                printf("选择错误,重新选择");
                break;
        }
        
    }while(input);
    return 0;
}

刚入手学习C语言,希望大家能关注一下我~~~~

如果喜欢这篇文章的话,请您给个赞,也欢迎收藏啊啊啊~~~

同时如果有不对的地方,请您在下面对话框里留言,我会及时改正的,谢谢。

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160954.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【基于机械臂触觉伺服的物体操控研究】UR5e运动学建模及代码实现

我的毕设题目定为《基于机械臂触觉伺服的物体操控研究》&#xff0c;这个系列主要用于记录做毕设的过程。 前言&#xff1a;UR系列是优傲公司的代表产品&#xff0c;也是目前比较通用的产品级机械臂。所以我打算用该机械臂进行毕设的仿真实现。关于其运动学建模&#xff0c;网…

【每日一题】【LeetCode】【第十二天】区域和检索 - 数组不可变

解决之路 题目描述 测试案例&#xff08;部分&#xff09; 第一次 emmm&#xff0c;说实话&#xff0c;一开始我还真没看懂题目是什么意思。。。。 自己按我自己理解的方式写了一下代码&#xff0c;用测试案例跑了下&#xff0c;成功了。 不过&#xff0c;放进去跑不通&…

VScode远程调试深度学习debug

VS Code CtrlP&#xff0c;在搜索框>select interpreter检查一下python环境 #查看GPU 环境&#xff1b;版本号 nvidia-smi.exe使用VSCode进行深度学习首先进行debug 首先要安装Remote Development个人理解可以远程打开编辑文件。 点击左下角的箭头&#xff0c;在对话框中…

async-excel整合站内信通知用户体验感满满

前面的文章我们讲过消息中心站内信的实现 【消息中心】 那么本章我们来说说异步导入导出完成后&#xff0c;如何使用消息中心站内信的功能进行通知用户业务处理完成了 在async-excel中异步逻辑处理完成后会调用一个callback方法进行回调&#xff0c;所以我们可以再对async-exc…

完全二叉树与堆(包含STL堆的用法)

完全二叉树 完全二叉树为一类特殊的二叉树&#xff0c;高度为h的完全二叉树满足如下条件&#xff1a; &#xff08;1&#xff09;所有叶结点都出现在第h或h-1层&#xff1b; &#xff08;2&#xff09;第h-1层的所有叶结点都在非叶结点的右边&#xff1b; &#xff08;3&#…

AAAI 2023|模拟人脑场景感知过程,套娃Transformer讲故事能力更上一层楼

原文链接&#xff1a;https://www.techbeat.net/article-info?id4467 作者&#xff1a;seven_ 视频字幕生成目前已成为工业界AI创作领域非常火热的研究话题&#xff0c;这一技术可以应用在短视频的内容解析和讲解中&#xff0c;AI讲故事的技术已经越来越成熟。而在学术界&…

13、ThingsBoard-如何发送告警邮件

1、概述 很多时候,我们使用thingsboard的时候,会遇到比如一个设备触发了告警,如何将设备的告警消息定义成邮件模板,然后通知租户或者客户管理员,管理员进行处理,这样的需求是非常重要的。 2、实现的步骤 要实现这个需求我总结了几步: 2.1、设备上报的参数与阈值进行…

基于关键点检测的病患步态检测及分析方法

在临床工作中&#xff0c;对患有神经系统或骨骼肌肉系统疾病而可能影响行走能力的患者需要进行步态分析&#xff0c;以评定患者是否存在异常步态以及步态异常的性质和程度 步态评定临床意义 1、评估患者是否存在异常步态以及步态异常的性质和程度 2、为分析异常步态原因和矫正异…

看我们应用性能监控如何几秒钟定位慢访问跟因

背景 某汽车集团的汽车配件电子图册系统是其重要业务系统。最近业务部门反映&#xff0c;汽车配件电子图册调用图纸时&#xff0c;出现访问慢现象。 某汽车集团总部已部署NetInside流量分析系统&#xff0c;使用流量分析系统提供实时和历史原始流量。本次分析重点针对汽车配件…

二进制?十进制!

链接&#xff1a;登录—专业IT笔试面试备考平台_牛客网 来源&#xff1a;牛客网 给定两个十进制整数 : AAA,BBB 你需要把它们的二进制形式以十进制的运算法则相加输出结果。 例如&#xff1a; A3,B2A 3 , B 2A3,B2 的时候&#xff0c;AAA 的二进制表示是 : 111111 , BB…

Linux部署Nexus通过Maven推送及拉取代码

&#x1f60a; 作者&#xff1a; 一恍过去&#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390&#x1f38a; 社区&#xff1a; Java技术栈交流&#x1f389; 主题&#xff1a; Linux部署Nexus通过Maven推送及拉取代码⏱️ 创作时间&#xff1a; 2023年…

myBaits Expert Wheat Exome — 从多个小麦品种中富集超过250Mb的高可信度的外显子

myBaits Expert Wheat Exome 与国际小麦基因组测序联盟(IWGSC)合作开发&#xff0c;使用了IWGSC发布的中国春基因组和注释信息。靶向六倍体小麦中完整的高置信度且有基因注释的外显子区域,能够全面、统一、可靠地深入覆盖大干15 Gb的小麦基因组中超过250 Mb的CDS及其邻近区域。…

硅烷聚乙二醇活性酯;Silane-PEG-NHS;溶于大部分有机溶剂。仅供科研实验使用,不用于诊治

英文名称&#xff1a;Silane-PEG-NHS&#xff0c;Silane-PEG-SCM 中文名称&#xff1a;硅烷聚乙二醇活性酯 分子量&#xff1a;1k&#xff0c;2k&#xff0c;3.4k&#xff0c;5k&#xff0c;10k&#xff0c;20k。。。 存储条件&#xff1a;-20C&#xff0c;避光&#xff0c;…

数组常用方法总结 (5) :find / findIndex / filter

find 与前边讲过的 some 类似&#xff0c;用于检测数组的每一项是否符合限定条件。只要遇到一个符合条件的&#xff0c;就会停止循环。在循环中&#xff0c;如果是简单数组&#xff0c;数据不会被改变&#xff0c;如果是对象数组&#xff0c;数据会改变。如果停止了循环&#…

音频(七)——数字麦克风和模拟麦克风(DMIC/AMIC)

数字麦克风与模拟麦克风(DMIC/AMIC) 麦克风(mic)&#xff1a;是将声音信号转换为电信号的能量转换器件&#xff0c;也就是用来采集你说话的声音扬声器(speaker)&#xff1a;是一种把电信号转变为声信号的换能器件&#xff0c;就是把对方说话产生的电信号转换成声音播放出来。简…

比较C++在for循环中的i++和++i以及i++的O2优化的效率:++i真的比i++快吗

比较C在for循环中的i和i以及i的O2优化的效率&#xff1a;i真的比i快吗 前言 对i和i的争论褒贬不一&#xff0c;不知从何时起&#xff08;大概是学C的时候老师就是这么教的&#xff09;我的习惯是在for循环中使用i而不是i for (int i 0; i < n; i) // 典但是看到一些博客…

再说多线程(五)——死锁

在前面四节中&#xff0c;我们一直没有讨论多线程程序的一个负面问题——死锁&#xff0c;有了一定的基础&#xff0c;现在是时候研究一下死锁了。死锁一定是出现在多线程程序中&#xff0c;单线程是不可能造成死锁的&#xff0c;因为你不可能同时加两把锁。死锁有个简单的例子…

《软件工程》课程四个实验的实验报告(《可行性研究与项目计划》《需求分析》《系统设计》《系统实现》)

实验1《可行性研究与项目计划》 实验学时&#xff1a; 2 实验地点&#xff1a; 任意 实验日期&#xff1a; 12月15日 一、实验目的 了解&#xff1a;软件项目可行性研究及项目计划的基本原理与方法&#xff1b;掌握&#xff1a;Visio等工具进行可…

【尚硅谷】Java数据结构与算法笔记06 - 算法复杂度详解

文章目录一、算法的时间复杂度1.1 度量算法执行时间的两种方法1.1.1 事后统计1.1.2 事前估算1.2 时间频度1.2.1 基本介绍1.2.2 举例说明&#xff1a;基本案例1.2.3 举例说明&#xff1a;忽略常数项1.2.4 举例说明&#xff1a;忽略低次项1.2.5 举例说明&#xff1a;忽略系数1.3 …

WebServer传输大文件致客户端自动关闭

程序运行在云服务器上, Ubuntu 20.04LTS系统&#xff0c;用浏览器测试能正常打开页面&#xff0c;请求一般的html文本和几十kb的小图片无问题&#xff0c;接着放了一个1.63MB&#xff08; 1714387字节&#xff09;的网上找的图过去&#xff0c;客户端图没加载完就自动断连了&am…