【C语言】-关于指针的知识你真的都知道了??快进来看看这里面的指针会让你眼前一亮

news2024/11/24 16:21:13

🎇作者:小树苗渴望变成参天大树
🎊作者宣言:认真写好每一篇博客
🎉 作者gitee:link
在这里插入图片描述如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

指针进阶

  • 💦前言
  • 💖一、字符指针
  • ✨二、指针数组
  • 💨三、数组指针
    • 💢3.1数组指针的定义
    • 💥3.2数组指针的使用
  • 💤四、数组参数,指针参数
    • 🎄4.1一维数组的传参
    • 🧨4.2二维数组传参
    • 💞4.3一级指针传参
    • 🎞️4.4二级指针的传参
  • 🎟️五、函数指针
    • ❤️‍🩹5.1函数指针的定义
    • 🎀5.2函数指针的使用
    • 🎗️5.3通过有趣的代码来更好的理解函数指针
  • 🎫六、函数指针数组
    • 🏀6.1函数指针数组的定义
    • 🌭6.2用法即用途
  • 🍟七、指向函数指针数组的指针
  • 🍔 八、总结


💦前言

各位友友们我们又见面了,今天博主继续给大家分享新的知识,并且干货很多,大家一定要好好的消化,我们今天讲的主题还是指针,不过是更高一级的指针,如果友友们连基本的指针都还没有搞得明白,可以去看看我浅谈指针那篇博客,不然看这篇博客会非常吃力。 话不多说,我们开始进入正文。


本篇重点

  1. 字符指针
  2. 数组指针
  3. 指针数组
  4. 数组传参和指针传参
  5. 函数指针
  6. 函数指针数组
  7. 指向函数指针数组的指针
  8. 回调函数
  9. 指针和数组面试题的解析
  1. 指针就是个变量,用来存放地址,地址唯一标识一块内存空间。
  2. 指针的大小是固定的4/8个字节(32位平台/64位平台)。
  3. 指针是有类型,指针的类型决定了指针的±整数的步长,指针解引用操作的时候的权限。
  4. 指针的运算。

在那篇博客中,我们大概只了解了指针的这些概念,接下来我们继续探讨更高级的指针

💖一、字符指针

字符指针就是指向字符的指针,在指针的类型中我们知道有一种指针类型为字符指针 char* ;

一.一种用法

int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

就是把字符的地址给一个指针变量,来存放它的地址,这个就是字符指针
(回忆:*就是告诉p是一个指针,char表示p指向的类型是字符类型)

二.这是字符指针的一种用法,他还有一种特别的用法:

int main()
{
    const char* pstr = "hello bit.";//这里是把一个字符串放到pstr指针变量里了吗?
    printf("%s\n", pstr);
    //这里在打印的时候不能写成*pstr,因为你这样只能获得首元素,但你又以字符串的形式打印,所以可能会出错。
    return 0;
}

字符串在内存的存储时连续的,把一个字符串赋给一个字符指针p里面,相当于把字符串里面的地址交给了p,那字符串地址又是什么呢?他其实就是首字符的地址,我们只需要访问首元素的地址,就能访问到整个字符串可以想象成数组,当不能当成数组。
在这里插入图片描述

这是一个常量字符串,时直接放在内存空间的,里面的内容不能同别人来访问,所以我们需要加一个const进行修饰,防止我们在写代码的时候被别人修改,写了的话,别人在修改的时候编译器就会报错,不然编译器就会崩掉的。

我们来看一道这样的面试题

#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;
}

women1laikan1
我们来分析一下:
在这里插入图片描述

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。相信,大家应该都明白了
那我们接下来在回顾一下之前的内容

✨二、指针数组

在《浅谈指针》博客中我们也学了指针数组,指针数组是一个存放指针的数组

char*p1="abcd";
char*p2="qwer";
我们把两个指针放在一个数组里面
char*arr[2]={p1,p2};
//优化后const char*arr[2]={p1,p2};
int i=0;
for(i=0;i<2;i++)
{
	printf("%d",arr[i]);
}
这样是不就可以打印每个元素了

我们把元素地址放在一个数组里面就构成了指针数组了,讲这个主要是和下面的知识点进行对应

💨三、数组指针

数组指针是指针?还是数组?
答案是:指针。
我们已经熟悉:
整形指针: int * pint; 能够指向整形数据的指针。
浮点型指针: float * pf; 能够指向浮点型数据的指针。
那数组指针应该是:能够指向数组的指针。

💢3.1数组指针的定义

我们在进行整型指针的时候,对于intp,先写一个变量,在变量前面加一个来告诉我们这个变量是一个指针,然后在加上所指向地址的类型是int,对于指针变量p来说他的类型是int*

那我们在写数组指针的时侯是不是要知道数组的类型是什么,在数据的存储的时候我提到过构造类型里面又数组,比如int arr[10],这个数组的类型是int [10];一次类推,去掉数组名就是数组的地址。

下面代码哪个是数组指针?

int *p1[10];
int (*p2)[10];
//p1, p2分别是什么?
int (*p)[10];
解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组(int [10]是数组类型)。所以p是一个
指针,指向一个数组,叫数组指针。


所以p1是指针数组,类型为int *[10]
p2是数组指针,类型为int(*)[10]

这里要注意:[]的优先级要高于号的,所以必须加上()来保证p先和结合。

那我们就来思考了,既然是指针那么就要存放地址了,那我们要存的是数组的地址,那我们来看看下面那个是正确的:

int arr[10]={0};
int (*p2)[10]=arr;//1
int (*p2)[10]=&arr//2

我们来分析第一个,arr表示首元素地址,首元素地址不能代表整个元素的地址啊,所以这个写法是错误的。
第二个,我们所过数组名在两种特殊情况是代表整个数组的地址,一个是&arr,一个是sizeof(arr),所以第二中写法是符合要求的。

💥3.2数组指针的使用

看代码:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int *p=arr;
    int i=0;
    for(i=0;i<10;i++)
    {
    	printf("%d",*(p+i));//打印每一个元素
    }
    return 0;
}

根据数组的地址是连续的性质,我们通过指针来访问我们的数组

那我们再来看一种访问数组的方式:

#include <stdio.h>
int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9,0};
    int (*p)[10] = &arr;
    int i=0;
    for(i=0;i<10;i++)
    {
    	printf("%d",(*p)[i]);//打印每一个元素
    }
    return 0;
}

我们在进行*p的时候得到整个数组里面的内容,有的人会说都得到里面的内容直接打印出来不就好了,为什么还要通过下标来访问,原因是这是一个整型数组不想字符串数组,这个没有结束标志,停不下来,所以我们需要通过下标循环的方式来访问数组里面的每一个元素。

那既然我们访问数组还是需要通过下边来访问数组里面的每一个元素,那我们为什么不使用简单一点的第一种呢?所以数组指针对于一维数组的使用就显得有点不太友好,所以我们经常在二维数组上去使用。


我们来看一段代码:

#include <stdio.h>
void print_arr1(int arr[3][5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
               printf("\n");
   }
}
void print_arr2(int (*arr)[5], int row, int col)
{
    int i = 0;
    for(i=0; i<row; i++)
   {
        for(j=0; j<col; j++)
       {
            printf("%d ", arr[i][j]);
       }
        printf("\n");
   }
}
int main()
{
    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
   print_arr1(arr, 3, 5);
    //数组名arr,表示首元素的地址
    //但是二维数组的首元素是二维数组的第一行
    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
    //可以数组指针来接收
    print_arr2(arr, 3, 5);
    return 0;
}

在这里插入图片描述
我们通过这个图片很好的知道了二维的数组指针怎么传参的,并且也知道怎么访问数组里面的每一个元素了吧

注意:
我们刚才提到二维数组的数组名是首元素的地址,也就是第一行的地址,那既然是地址,我们为什么不能使用一级指针存储呢??原因是一级指针只能存储一个元素的地址,不能存放多个元素的地址,只能使用指针数组来存储多个元素的数组。数组指针不受数组里面元素的限制

学了指针数组和数组指针我们来一起回顾并看看下面代码的意思:

int arr[5];
int *parr1[10];
int (*parr2)[10];
int (*parr3[10])[5];

1.arr是一个数组里面有5个元素,每个原不俗的类型为int
2.parr1是一个指针数组,里面有10个元素,每个元素的类型是int *
3.parr2是一个数组指针,指向的数组里面有10个元素,每个元素的类型是int。 parr2的类型为int()[10]
4.parr3先和[]结合是一个数组里面有10个元素,每个元素是一个数组指针(相当于一个数组的整个地址,这个数组有5个元素)parr3这个数组的类型是int(
)[5]
在这里插入图片描述不懂的可以看看这个图片理解一下

因为是数组指针,涉及到数组,数组就两种形式,对一维数组不太友好,所以我们掌握二维数组的数组指针怎么使用就好了。我们接下来在借一个新的知识点

💤四、数组参数,指针参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数该如何设计呢?

🎄4.1一维数组的传参

#include <stdio.h>
void test(int arr[])//ok?
{}//用数组接收肯定是没问题的
void test(int arr[10])//ok?
{}//这个更没有问题了
void test(int *arr)//ok?
{}//传过来是arr首元素地址,用一级指针接收没有问题
void test2(int *arr[20])//ok?
{}//本身就是一个指针数组,用指针数组接收,没有问题。
void test2(int **arr)//ok?
{}//传过来是首元素地址,首元素的类型是int*类型,相当于传过来一级指针的地址,用二级指针接收,没有问题
int main()
{
 int arr[10] = {0};
 int *arr2[20] = {0};
 test(arr);
 test2(arr2);
}

🧨4.2二维数组传参

void test(int arr[3][5])//ok?
{}//这是没问题的
void test(int arr[][])//ok?
{}//这个不行
void test(int arr[][5])//ok?
{}//这个可以
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?
{}//传过来是二维数组的数组名是第一行的地址,不能使用一级指针存储,只能使用数组指针
void test(int* arr[5])//ok?
{}//这里面是一个指针数组,传过来的是地址,应该用指针接收,所以错误
void test(int (*arr)[5])//ok?
{}//这个可以
void test(int **arr)//ok?
{}//传过来是第一行地址,是int类型的地址,二级指针只能接收int*类型的地址,所以不可以
int main()
{
 int arr[3][5] = {0};
 test(arr);
}

💞4.3一级指针传参

#include <stdio.h>
void print(int *p, int sz)
{
 int i = 0;
 for(i=0; i<sz; i++)
 {
 printf("%d\n", *(p+i));
 }
}
int main()
{
 int arr[10] = {1,2,3,4,5,6,7,8,9};
 int *p = arr;
 int sz = sizeof(arr)/sizeof(arr[0]);
 //一级指针p,传给函数
 print(p, sz);
 return 0;
}

千万参数里面不能写成二级指针,我传的一级指针,不是在一级指针的地址。

🎞️4.4二级指针的传参

#include <stdio.h>
void test(int** ptr)
{
 printf("num = %d\n", **ptr); 
}
int main()
{
 int n = 10;
 int*p = &n;
 int **pp = &p;
 int *arr[10];
 test(arr)//arr是首元素的地址,因为这个数组里面的每个元素的类型是int*的,所以用二级指针接收时没问题的
 test(pp);
 test(&p);
 return 0;
}

说明 pp=&p *pp=p **pp=*p

我们掌握好参数的传参,相信大家对我刚才所说的知识点都应该有了一个更好的理解,我们接下来再来学习一个新的知识点

🎟️五、函数指针

❤️‍🩹5.1函数指针的定义

函数指针就是指向函数的指针,把函数的地址交给一个指针,就构成了函数指针
那我们怎么去定义函数指针呢??
还是老问题,我们需要知道函数的类型,才能作为函数的类型,比如
int add(int x,int y);这个函数的类型是 int (int,int);去掉函数名就是函数的类型,我们来看两种定义:

int add(int x,int y)
{
	return x+y;
}
//定义1
int (*p1)(int,int);
//定义2
int *p1(int,int);

答案是定义1是正确的,意思是先和p1结合,说明是一个指针,这个指针指向的类型是int (int,int);
但是定义2是一个函数的声明,这个函数有两个参数,第一个参数为int,第二个参数也为int,返回类型为int
.

注意,不要写成这样:int (int,int) *p,这样写是错误的,这个等会在typedef的时候也是相同的写法。并参数里面的变量可写可不写

我们知道了怎么定义了,那我们怎么使用它呢??

int add(int x,int y)
{
	return x+y;
}
int (*p1)(int,int)=&add;

这么写肯定没有问题,把add函数的地址取出来放到指针里面。意思就是p1是一个函数指针,指向的函数有两个参数,一个为int,一个也为int,返回类型为int.

那我们再来看一个代码

#include <stdio.h>
void test()
{
 printf("hehe\n");
}
int main()
{
 printf("%p\n", test);
 printf("%p\n", &test);
 return 0;
}

在这里插入图片描述
我们看到函数名和取地址函数名是相等的,指针里面存放的是指向函数的地址,既然取地址函数名的地址和函数名的地址是一样,那我们可不可以这么去使用??

int (*p1)(int,int)=add;

答案是可以,我们把地址放到指针里面,主要是想用指针来操作函数,那这两种方法放到地址都是一样的,那么进行指针操作的效果也应该是一样的,那我们具体来看看怎么使用函数指针来操作函数吧

🎀5.2函数指针的使用

int add(int x,int y)
{
	return x+y;
}
int main()
{
//第一种
int (*p1)(int,int)=&add;
int sum=(*p1)(2,3);
//第二种
int (*p1)(int,int)=&add;
int sum=p1(2,3);
printf("%d",sum);
return 0;
}

在这里插入图片描述
第一种我们先进行解引用p1找到函数的内容在进行操作,相当于p=add,也就是(*p)(2,3)=add(2,3);
第二种那本身p就是&add,&add=add,那么p=add,所以p(2,3)=add(2,3),

所以星号和取地址操作符在这就是一个摆设,加星号和取地址更符合我们之前的理解,在函数指针里面,*可以不加,如果要加,必须带括号,不然就会出现问题例如:*p(2,3)这个先调用然后返回一个整数,你在对整数解引用会发生错误。

🎗️5.3通过有趣的代码来更好的理解函数指针

//代码1
(*(void (*)())0)();
//代码2
void (*signal(int , void(*)(int)))(int);

在这里插入图片描述
大家可以对着图和自己的理解去在好好的分析这两个代码想表达的意思,
我们看到代码2它复杂了我们如何进行优化呢??

typedef void(*)(int) pfun_t;
常规是这样重命名的,但是在这里不行,必须写成下面的这种形式,数组指针的重命名也是和下面的一样。
typedef void(*pfun_t)(int);

我们在来看这个代码是不是更好的理解了呢??
pfun_t signal(int, pfun_t);

我们接下来讲新的知识点

🎫六、函数指针数组

🏀6.1函数指针数组的定义

我们在之前学过指针数组例如intarr[10],chararr[10]。其实他们准确来说叫整型指针数组,或叫字符指针数组,是指向存放整型指针的数组,或叫存放字符指针的数组,那我们来类比函数指针数组,就是存放函数指针的数组,函数指针就是存放数组的地址,所以意思就是把多个函数的地址放在一个数组里面。我们定义一个数组,数组里面假设5个元素,类型为里面元素的类型,即为函数指针类型,那让我们来看看它具体是怎么定义的吧

int (*parr1[10])();
int *parr2[10]();
int (*)() parr3[10];

答案是:parr1
parr1 先和 [] 结合,说明 parr1是数组,数组的内容是什么呢?
是 int (*)() 类型的函数指针。
第二个啥也不是,大家自己分析,第三个表示有问题,必须放在括号里面和星号离得近才行。

🌭6.2用法即用途

我们大致知道了函数指针数组的定义了那我们怎么去使用呢??
常见的用途:转移表,
我们来举一个例子,我们来写一个只能实现加减乘除的计算器吧:

#include <stdio.h>
int add(int a, int b)
{
 return a + b;
}
int sub(int a, int b)
{
 return a - b;
}
int mul(int a, int b)
{
 return a*b;
}
int div(int a, int b)
{
 return a / b;
}
int main()
{
 int x, y;
 int input = 1;
    int ret = 0;
    do
   {
        printf( "*************************\n" );
        printf( " 1:add           2:sub \n" );
        printf( " 3:mul           4:div \n" );
        printf( "*************************\n" );
        printf( "请选择:" );
        scanf( "%d", &input);
        switch (input)
       {
        case 1:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = add(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 2:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = sub(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 3:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = mul(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 4:
              printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = div(x, y);
              printf( "ret = %d\n", ret);
              break;
        case 0:
                printf("退出程序\n");
 breark;
        default:
              printf( "选择错误\n" );
              break;
       }
 } while (input);
    
    return 0;
}

我们可以通过case语句来分别调用每个函数,但是我们有没有想过一个问题,这个计算器只有四个功能,我们正常的计算器有许多功能,所以我们在添加更多的功能的时候,我们需要增加更多的case语句,这样使得代码会变得特别长,那我们用case语句·实现的思想也就是通过操作数来实现,操作数是连续,那我们可以不可以把操作数想象成数组下标,然后访问数组里面的元素(即为对应的函数),那我们来改造代码。

我们写一个函数指针数组,来存放函数指针。

int(*p[5])(int,int)={NULL,add,sub,mui,div};

通过访问下标来访问里面的元素,例如 p[1](2,3);就是调用假发这个函数。以此类推。

改造后的代码为:

  int x, y;
     int input = 1;
     int ret = 0;
     int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
     while (input)
     {
          printf( "*************************\n" );
          printf( " 1:add           2:sub \n" );
          printf( " 3:mul           4:div \n" );
          printf( "*************************\n" );
          printf( "请选择:" );
      scanf( "%d", &input);
          if ((input <= 4 && input >= 1))
         {
          printf( "输入操作数:" );
              scanf( "%d %d", &x, &y);
              ret = (*p[input])(x, y);
         }
          else
               printf( "输入有误\n" );
          printf( "ret = %d\n", ret);
     }
      return 0;
}

后来我们要添加其他相同参数和返回类型的函数时候,就可以直接往数组里面加元素,使代码量还是这么多,这个方法也叫转移表。希望大家能了解,并且能用它写出好的代码才是最重要的。

🍟七、指向函数指针数组的指针

相信大家看到这个标题的时候已经茫然了,不要着急,我就是来给你解决这种茫然的,我们学过数组指针,就是把数组的地址用指针存放起来,那么,函数指针数组是不是也叫数组,也使把数组的地址用指针来存放,我们需要一步步的来写

先是函数指针–函数指针数组–函数指针数组的指针。

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)[5])(const char*) = &pfunArr;
 return 0;
}

在这里插入图片描述
我们通过这个图可以很快的知道了怎么通过函数指针数组来写指向函数指针数组的指针

其实这些东西理解起来不难,我们只要弄清楚到底是是和星号结合还是[]这个操作符结合,就能知道是指针还是数组了,我们一定要从最基础的往深的方向的分析,不能直接一上来就开始分析,往往把自己弄糊涂了,还希望读者可以自己去理解和分析,等我们把弄懂了,博主在出一期关于这个知识点题目的博客,方便大家去练习。

🍔 八、总结

今天终于把关于指针的所有知识点讲完了,当我们理解好这些知识点的时候,我们会发现,我们对于指针的问题会变得不在害怕,因为我们不在感到陌生,所以我也希望各位读者能通过我的博客真真实实的学到东西,并且也希望各位友友给我点点赞,让更多的人能通过这篇博客受益匪浅,那我们今天就讲到这里了,我们下篇再见!!
在这里插入图片描述

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

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

相关文章

MYSQL 数据行列交换(交换数据) 存储过程实现

行列转换的存储过程 初始图&#xff1a; 效果图&#xff1a; 实现过程&#xff1a; 1&#xff1a;建表、建数据 CREATE TABLE changeprice ( id bigint(20) NOT NULL auto_increment, sid bigint(20) NOT NULL, datecreated timestamp NOT NULL defaultCURRENT_TIMESTAMP…

nvue2中使用axios和插槽

一、组件的生命周期1、组件的生命周期经历的阶段&#xff1a;&#xff08;1&#xff09;创建阶段&#xff1a;beforeCreate、created、beforeMount、mounted&#xff08;2&#xff09;运行阶段&#xff1a;beforeUpdate、update&#xff08;3&#xff09;销毁阶段&#xff1a;2…

C语言二维数组和二重指针详解

二维数组 一个二维数组&#xff0c;在本质上&#xff0c;是一个一维数组的列表。声明一个 x 行 y 列的二维整型数组&#xff0c;形式如下&#xff1a; type arrayName [x][y]; 这个表示&#xff0c;有x个一维数组&#xff0c;每个一维数组的元素个数是y个。 声明示例&#xff1…

手写VITE-MockJS插件

mockJS是什么 mockJS是模拟接口数据&#xff0c;拦截客户端的请求的一个工具。 vite插件编写流程 pnpm init 初始化 pnpm install vite -D 安装Vite 配置package.json文件中的脚本 "scripts": {"dev": "vite","build": "vite…

K8s集群离线安装-kubeadm-详细篇

1、部署k8s的两种方式&#xff1a;kubeadm 和二进制源码安装 #本次实验采用的部署Kubernetes方式&#xff1a; kubeadm Kubeadm是一个K8s部署工具&#xff0c;提供kubeadm init和kubeadm join&#xff0c;用于快速部署Kubernetes集群。2、环境准备 #服务器要求&#xff1a; 建…

JavaSE学习day1_02, JDK安装

1.4 环境变量 1.4.1 为什么配置环境变量 在初次学习编程的时候,环境变量我们经常提到,但是环境变量到底是什么?是干嘛的?弄清楚这些问题很重要. 如果我们想要在CMD的任意路径下都可以打开任意的软件&#xff0c;那么就需要把软件的路径配置到环境变量当中。 为了便于大家…

Zotero | 快速入门

文章目录0. 前言1. Zotero快速入门1.1 下载Zotero和Connector2.2 联动sci-hub实现英文文献批量下载2.3 英文文献翻译2.4 中文文献元数据识别2.5 Zotero其他配置2.5.1 语言切换2.5.2 数据存储位置更改参考0. 前言 Zotero是一款自由及开放源代码的文献管理软件&#xff0c;管理书…

告别2022,喜迎2023

2022只剩下最后几天&#xff0c;新的一年即将拉开序幕。你的2022&#xff0c;是苦尽甘来、柳暗花明&#xff0c;还是安适如常、平安喜乐?你会用什么词来形容你的2022&#xff0c;你期待的2023又是什么样的呢? 时光荏苒&#xff0c;岁月悠悠&#xff0c;转眼间&#xff0c;202…

猿代码超算实习生计划之编程语言分析

编程语言特别多&#xff0c;很多同学都特别纠结以后到底选择哪一种编程语言&#xff0c;才好找高薪实习和工作。 其实编程语言没有好坏之分。很多同学选择未来就业和实习方向时很多也都是参考的薪资和岗位数量。就像前几年很多人选的前端、现在卷到不行的Java、还有今年连实习…

【Cortex-A7核PWM实验】

Cortex-A7核PWM实验 ---蜂鸣器、风扇、震动马达PWN概念PWM硬件电路图如何产生PWM方波捕获/比较寄存器工作原理代码实现PWN概念 PWM是指脉冲宽度调制&#xff08;Pulse Width Modulation&#xff09;&#xff0c;是一种常用的模拟信号转换为数字信号的方法。 1.脉冲&#xff1a…

C#WinForm实现多语言切换

因项目需要&#xff0c;所以在网上找了一些方法实现了该功能&#xff0c;本文也是做一个总结和记录。使用resx文件实现Winform多语言切换&#xff0c;以实现简体中文、英文、泰语的切换为例。如果后续需要增加其它语言的切换&#xff0c;只需要按照步骤重复操作即可。 效果图如…

【Kotlin】函数 ⑨ ( Kotlin 语言中的闭包概念 | Java 语言中函数作为参数的替代方案 )

文章目录一、闭包概念二、Java 中函数作为参数的替代方案 ( 匿名内部类 )一、闭包概念 匿名函数 就是 Lambda 表达式 , 同时也是 闭包 , 三者的是相同的概念 ; 闭包意义 : 在 Java 中 , 通过 Package 包 , Class 类 , 将作用域区分开 , 将变量 定义在 不同的 包 或 类中 , 可…

阿里云创世纪之盘古传奇

文章目录飞天(Apsara)云计算平台简介面向私有云的Apsara Stack盘古横空出世盘古的架构盘古基本介绍盘古API基于C语言的SDK基于命令行的文件操作接口pu盘古中的目录和文件盘古目录盘古中的文件盘古中的文件类型盘古应用场景盘古的功能特性盘古主要性能盘古的数据安全盘古的边界盘…

Docker - Docker网络

一、Docker网络介绍 Docker是基于Linux Kernel&#xff08;内核&#xff09;的namespace&#xff0c;CGroups,UnionFileSystem等技术封装成的一种自定义容器格式&#xff0c;从而提供了—套虚拟运行环境。 1、namespace&#xff1a;用来做隔离的&#xff0c;比如pid[进程].、…

Java多线程案例之阻塞队列

文章目录一. 认识阻塞队列1. 什么是阻塞队列2. 生产者消费者模型3. 标准库中阻塞队列类二. 基于循环队列实现的简单阻塞队列1. 循环队列的简单实现2. 阻塞队列的简单实现一. 认识阻塞队列 1. 什么是阻塞队列 阻塞队列本质上还是一种队列, 和普通队列一样, 遵循先进先出, 后进…

291. 蒙德里安的梦想(状态压缩dp详解)

求把 NM 的棋盘分割成若干个 12 的长方形&#xff0c;有多少种方案。 例如当 N2&#xff0c;M4 时&#xff0c;共有 5 种方案。当 N2&#xff0c;M3 时&#xff0c;共有 3 种方案。 如下图所示&#xff1a; 输入格式 输入包含多组测试用例。 每组测试用例占一行&#xff0c…

龙芯机器JDK安装和配置

龙芯机器&#xff1a;[rootlocalhost j2sdk-image]# cat /proc/cpuinfo | grep name | cut -f2 -d: | uniq -c4 Loongson-3A R4 (Loongson-3A4000) 1800MHz龙芯机器JDK安装和配置下载地址&#xff1a;http://www.loongnix.cn/zh/api/java/选择JDK8&#xff0c;选择MIPS64进行下…

2-3进程管理-进程同步

文章目录一.进程同步、互斥二.实现临界区互斥的基本方法&#xff08;一&#xff09;软件实现方法&#xff08;二&#xff09;硬件实现方法三.互斥锁四.信号量机制五.经典同步问题&#xff08;一&#xff09;生产者-消费者问题&#xff08;二&#xff09;读者-写者问题&#xff…

流逝的一年

昨天远方的大哥打来了电话&#xff0c;我们聊了下近况。当他问及去年是否有新的著作问世&#xff0c;我不禁有些赧然&#xff0c;解释说还在学习中… 放下电话后&#xff0c;我陷入了思索&#xff1a;又是一年划上了句号&#xff0c;这一年我做了什么&#xff1f;我又有什么收…

数据库的一些基本概念

一、服务器&#xff1a;&#xff08;更正大家头脑中的一个错误认识&#xff09; 1、服务器是一种软件&#xff0c;不是硬件&#xff0c;不是计算机。 2、不同服务器负责调用不同类型的文件。 二、表文件、数据库、数据库服务器以及SQL语句&#xff1a; 1、表文件: …