C语言进阶之我与指针的爱恨情仇(1)

一.前言

        我们在初阶《指针》初阶C语言-指针-CSDN博客已经讲过了一些基础知识,知道了关于指针的一些概念->

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

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

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

4.指针的运算

        接下来,我们继续探讨指针更深层的秘密->

二.字符指针

        指针有一种类型叫做指针类型——char*。char* 的两种常见用法->

#include <stdio.h>int main()
{char ch = 'w';char* pc = &ch;"abcdef";//这是一个字符串,当它做一个表达式时,它的值是首字符的地址值。char* ps = "abcdef";//所以可以用字符指针来接收return 0;
}

        "abcdef"也可以看成是一个数组(内存中的一块连续空间),而这个整体可以看成数组名(因为拿到了数组名就相当于拿到了数组首元素地址,这两个是等价的),所以可以做以下操作->

        要访问这段空间,可以->

        但事实上,这样写是有风险的,因为ps指向的对象是一个常量字符串,它不是变量,是不能被修改的,所以为了不小心对ps解引用使用写权限,程序就会崩溃->

        退出码不是0,说明程序不是正常退出。 所以为了安全,我们应该使用const修饰ps->

        所以,不论说字符指针指向的是字符串的首元素还是字符串都是正确的,都是能正常访问的。 但是需要注意的是,ps存的是字符串首元素的地址,它并不能把整个字符串存进去,因为有首元素的地址加之字符串在内存空间中是连续的,会误以为,ps存入了整个字符串。

        下面有一道面试题,问最后的输出是什么->

#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");elseprintf("str1 and str2 are not same\n");if (str3 == str4)printf("str3 and str4 are same\n");elseprintf("str3 and str4 are not same\n");return 0;
}

         str1和str2是两个数组,分别代表了内存中的两块不同空间,当if  str1 == str2时,数组名比较,比较的是地址是否相同,因为二者是内存种不同的两块空间(虽然存的内容是一样的),所以地址是不一样的。对于str3和str4,它们指向的是常量字符串,所以内存中肯定有一块空间,是用来存放这个常量字符串的,由于,常量字符串是只有可读属性,不具有可写属性,所以是没必要将一份相同的常量字符串保存两份的,因此,str3和str4存的都是字符串首元素的地址,二者相等是没问题的。

三.指针数组

        指针数组是数组,由字符数组 - 存放字符的数组、整型数组 - 存放整型的数组,类比出指针数组 - 存放指针的数组,存放的元素在数组中的元素都是指针类型的。

int* arr1[5];    //整型指针的数组
char* arr2[10];    //字符指针的数组

        但是指针数组可以用来模拟二维数组,关于这方面的介绍可以看初阶C语言-指针-CSDN博客->

int main()
{int arr1[5] = { 1, 2, 3 ,4 ,5 };int arr2[5] = { 2, 3, 4 ,5 ,6 };int arr3[5] = { 3, 4, 5 ,6 ,7 };int* arr[3] = { arr1, arr2, arr3 };return 0;
}
#include <stdio.h>int main()
{char* arr[3] = { "张三", "李四", "王五" };int i = 0;for (i = 0; i < 3; ++i){printf("%s ", arr[i]);}return 0;
}

 四.数组指针

        数组指针是指针,也可以由字符指针 - 指向字符的指针,整型指针 - 指向整型的指针,浮点型指针 - 指向浮点数的指针类比出数组指针 - 指向数组的指针。

#include <stdio.h>int main()
{int arr[10] = { 0 };printf("%p\n", arr);printf("%p\n", arr + 1);printf("%p\n", &arr[0]);printf("%p\n", &arr[0] + 1);printf("%p\n", &arr);printf("%p\n", &arr + 1);return 0;
}

        我们发现,数组的地址和数组的首元素地址是一样的,但他们的区别在于->数组首元素地址+1,走了4字节,数组的地址+1走了40个字节,因为指针的类型决定了指针+1,到底+几个字节,所以能看到,数组首元素的地址是int*,数组的地址是数组指针类型。那我们应该用什么类型的指针来接受&arr呢->

int* p[10] = &arr;

        这样写是不可以的,这样写的话,p就是指针数组,p会先跟[]结合形成数组,int*是数组的元素类型,所以应该是*跟p优先结合成指针,指向的类型是int [10]->

#include <stdio.h>int main()
{int arr[10] = { 0 };int(*p)[10] = &arr;printf("%p", p);return 0;
}

        语法规定了是这样写,注意不要写成int[10] (*p);关于int(*p)[10],可以理解成指针p指向了一个有10个元素的数组,每个元素的类型是int。

        通过报错信息可以发现,在定义数组指针的时候,指向的数组大小必须指定(且是常量),否则编译器会默认为0。 

        接下来讲解一些数组指针的应用场景->

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

        (*p)等价于(*&arr),然后*和&相抵消,得到arr,数组名即数组首元素的地址,但这样访问数组明显是不方便的,不常用的,没有int *p = arr方便。 

        二维数组之前是这样打印的,二维数组传参,形参也是使用二维数组的形式,之前提过,此时数组名(实参)是数组首元素的地址,那在形参就可以写成指针的形式(这也是数组传参的本质),先复习一下一维数组传参->

         此时,形参写的也是数组形式,本质上传arr数组名,即数组首元素的地址,那还原回去用指针接收也是没问题的->

         再回到刚刚的二维数组传参,二维数组的数组名,它到底是谁的地址?到底用什么类型的指针接收?二维数组的首元素是它的第0行(arr[0]),所以二维数组的数组名是第0行的地址即&arr[0],arr[0]是第0行的数组,所以用成指针做形参,它应该是指向一个数组,应该用数组指针->

#include <stdio.h>void Print(int (*p)[5], int r, int c)
{int i = 0;for (i = 0; i < 3; i++){int j = 0;for (j = 0; j < 5; j++){printf("%d ", p[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;
}

         p是一行数组的地址,它+i(步长),走的是i行,对其*拿到第i行首元素的地址,+j,才能访问那一行前j个元素。(也就是说当p是一行数组的地址时,它的下一个元素也是一行数组,只有*p拿到那一行首元素的地址,才能方便访问那一行的元素。同时也应该知道p是一个数组指针,对其解引用,得到的是其首元素的地址,因为p = &arr,*p = arr)。

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

int arr[5];    //数组
int *parr1[10];    //指针数组
int (*parr2)[10];    //数组指针
int (*parr3[10])[5];    //存放数组指针的数组

        最后一个是parr3先跟[10]结合表示是数组,数组元素是int (*)[5],即数组指针->

 五.数组参数、指针参数

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

5.1一维数组传参

#include <stdio.h>#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int* arr)//ok?
{}
void test2(int* arr[20])//ok?
{}
void test2(int** arr)//ok?
{}int main()
{int arr[10] = { 0 };int* arr2[20] = { 0 };test(arr);test2(arr2);return 0;
}

第一个形参的部分写成数组,本质是指针,所以[]可以不用写大小,是对的。

第二个也是对的,实事上,不会真的去创建一个数组接收,大小是没有意义的,写多少都可以。

第三个是一维数组传参的本质,它传递的是数组首元素的地址,所以也是对的。

第四个,形参用数组的形式接收是没问题的。

第五个,传递的是数组首元素的地址,由于数组首元素是指针,所以用二级指针接收指针的地址,没问题。

5.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 main()
{int arr[3][5] = { 0 };test(arr);
}

第一个就是数组传参,形参写成数组的形式,是没问题的。

第二个第三个是行可以省略,列不能省略,且列必须是5,因为本质是int(*arr)[5],这里的[5]要和int arr[][5]对应上,列修改成6,那个数组指针指向的数组元素也会是6。

第四个不行的,因为指针类型不一样。

第五个也是不可以的,形参是数组指针都不是指针类型。

第六个是正确的形式。

第七个形参是二级指针,指针类型也没对上。

5.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;
}

        一级指针传参,形参写成一级指针即可。

思考:当一个函数的参数部分为一级指针时,函数能接收什么参数呢?

void test(int *p)
{///...
}
int a = 10;
int* ptr = &a;
int arr[10] = { 0 };
test(&a);
test(ptr);
test(arr);

         只要能传一个整型指针类型过去就行。

5.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[5];test(pp);test(&p);test(arr);return 0;
}

思考:当函数的参数为二级指针时,可以接受什么参数?

void test(char **p)
{}int main()
{char c = 'b';char*pc = &c;char**ppc = &pc;char* arr[10];test(&pc);test(ppc);test(arr);//Ok?return 0;
}

 六.函数指针

数组指针 - 指向数组的指针 - 存放的是数组的地址,&数组名就是数组的地址。

函数指针 - 指向函数的指针 - 存放的是函数的地址,那怎么的到函数的地址呢?

        数组名是数组首元素地址,那函数名呢?是函数的地址吗? 

#include <stdio.h>int test(int x, int y)
{return x + y;
}int main()
{printf("%p\n", test);printf("%p\n", &test);int (*pf1)(int, int) = test;int (*pf2)(int, int) = &test;return 0;
}

         ->&函数名和函数名都是函数的地址,没什么区别。

        假设我要用一个函数指针变量pf1来接收test函数的地址,那我首先是pf1 = test,因为pf1是指针,->(*pf1),函数要函数作用符(),参数列表和返回类型吧->int (*p) (int int) = test。

        同时也是没有任何的警告,说明test和&test类型是一致的。

        注意:(*pf1)的括号是不可以省略的,如果写成int *pf1(int, int),它就变成了一个函数声明(pf2是函数名,返回类型是int*)。 同时,函数指针的参数只需要标明参数类型(也可以写成(int x, int y)),因为没有调用函数,并没有传参的过程发生。

        接下来就是如何利用pf1来调用函数->

        *pf1找到函数的地址,调用函数,再开始传参...函数栈帧结束,返回值被寄存器返回回来,被ret接收。 但其实*pf1的*是多余的,写或者不写都可以,甚至想写几个就写几个->

         这个和一维数组形参,int arr[100]的这个100就是个摆设,不写或者写成100000都行。但是注意别写成*pf1(2,5),优先级得注意。

        阅读两段有趣的代码->

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

第一个是先看认识的0,0的左边是(void (*)()),里面是函数指针类型,->(类型)0,就是把0强转成函数指针类型,再*解引用,即调用地址0处的函数,至于最右边的()表示调用的函数无参,是因为0被强转成函数指针类型的时候,函数的参数是(),无参。

第二个先看signal,先跟()结合,说明是个函数,然后看()内是只有类型,说明是函数声明,它的参数是int类型和函数指针类型,该类型是void(*)(int),函数的返回类型也是void (*)(int)。

         返回类型可以写成void (*) (int) signal(int, void(*)(int))吗?虽然明面上不可以,但是可以类型重命名->

typedef void (*)(int) pfun_t;//这是错误的
typedef void (*pfun_t)(int);//这是对的,理解的时候可以按照上面那一种方式理解,但不能那样写。

void (*pfun_t)(int);

         此时,pfun_t是函数指针变量。

typedef void (*pfun_t)(int);

        此时,pfun_t是函数指针类型。typedef是类型重定义。

七.函数指针数组

指针数组 - 数组

char* arr1[5];        //字符指针数组 - 存放的是字符指针

int* arr2[5];        //整型指针数组 - 存放的是整型指针

函数指针数组 - 数组

//存放的是函数指针即函数的地址

         由之前的函数指针知识可以知道,函数指针类型是 type_t (*)(type_t...)。

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

         我们知道pf1和pf2的类型都是int (*)(int, int),即他们是同一种类型,即可以用数组来存储,数组类型 + 数组名->int (*pfArr[2])(int, int) = { &Add, &Sub };。

        函数指针数组的用途就是:转移表

eg:计算器

#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;int x = 0;int y = 0;int ret = 0;do{menu();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");break;default:printf("选择错误,重新选择\n");break;}} while (input);return 0;
}

        随着计算器的函数越来越多,这个switch分支语句就越来越多,而且代码有点重复->

        这些函数有一些特点,除了函数名不一样,函数返回值和参数是一样的,所以可以改造成-> 

#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;int x = 0;int y = 0;int ret = 0;do{menu();printf("请输入:>");scanf("%d", &input);//函数指针数组int (*pfArr[])(int, int) = { NULL, &Add, &Sub, &Mul, &Div };if (input == 0){printf("退出计算器\n");}else if (input >= 1 && input <= 4){printf("请输入两个操作数\n");scanf("%d%d", &x, &y);ret = pfArr[input](x, y);printf("ret = %d\n", ret);}else{printf("选择错误,请重新选择\n");}} while (input);return 0;
}

         后续如果要加功能,直接在菜单上多添加几个选项,在函数指针数组后面接着续函数地址即可。但是这样的缺点是,函数的返回类型和参数必须是一样的,才能放入函数指针数组内。此时我们把这种情况下的函数指针数组叫做转移表。

八.指向函数指针数组的指针

        指向函数指针数组的指针是一个指针指向一个数组,数组的元素都是函数指针。

	int a = 10;int b = 20;int c = 30;int* arr[] = { &a, &b,&c };//整型指针数组int* (*p)[3] = &arr;//p是指针,是指向整型指针数组的指针

函数指针数组 - 数组 - 存放的是函数的地址,如刚刚写过的一个->int (*pfArr[])(int, int);,pfArr是函数指针数组,&pfArr,就需要函数指针数组指针来指向它->int (*(*p)[])(int, int) = &pfArr;

        这个比较难,不太重要,这个阶段基本用不到。

九.回调函数 

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

        我们能有办法把上述四个case语句的函数封装成一个函数吗?这四段代码只有调用的函数不一样,所以我们能够封装成一个calc()函数,参数就传对应函数地址就完成了任务。 

void calc(int (*pf)(int, int))
{int x = 0;int y = 0;int ret = 0;printf("请输入两个操作数\n");scanf("%d%d", &x, &y);ret = pf(x, y);
}

         当指针pf调用Add函数时,Add函数就被称为回调函数。这里的实现方是Add函数,我们没有直接调用Add函数,而是在特定的的事件发生(input == 1),由另一方calc函数通过函数指针调用。

        再来演示一下qsort函数的使用->

qsort - 是一个库函数,底层使用的是快速排序的方式,对数据进行排序的这个函数可以直接使用,这个函数可以用来排序任意类型的数据。

排序算法有很多,我们当前学过的有冒泡排序,先回忆一下冒泡排序->

#include <stdio.h>void bubble_sort(int arr[], int sz)
{int i = 0;for (i = 0; i < sz - 1; ++i){int j = 0;for (j = 0; j < sz - i - 1; ++j)//每一趟的比较次数{if (arr[j + 1] < arr[j]){int temp = arr[j];arr[j] = arr[j + 1];arr[j + 1] = temp;}}}
}void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; ++i){printf("%d ", arr[i]);}
}int main()
{int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 };int sz = sizeof arr / sizeof arr[0];bubble_sort(arr, sz);//核心思想是两两相邻元素比较print_arr(arr, sz);return 0;
}

        这个函数不好的地方在于,参数是写死的,只能排序int类型的数组。而qsort函数是可以排序任意类型的数据的,我们先来看看qsort是如何做到的->

void qsort (void* base,//待排序数组的第一个元素的地址size_t num,//待排序数组元素个数size_t size,//待排序数组一个元素的大小int (*compar)(const void*,const void*)//函数指针- 指向一个函数,这个函数是用来比较两个元素的);

        第四个参数是用来告诉qsort我应该怎么来比较大小的,因为比较不同的数据的大小方式是不一样的。所以你只要提供了比较大小的方法,传给compar即可。

         函数指针compar的参数是两个待比较元素的地址。void*的指针不能直接解引用,也不能进行+-整数操作,它的作用是用来存放任意类型数据的地址->

char c = 's';
char* pc = &c;
int* p = &c;//两边数据类型不一样,vs报警告
void* pc_ = &c;//这个不报警告
int a = 20;
pc_ = &a;//不报警告

        void*是无具体类型的指针,可以接收任意类型的地址,它就像个垃圾桶一样。

        compar的返回值是有要的, 1大于p2时返回大于0的数,等于则返回0,小于则返回<0的数。

int com_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}
#include <stdio.h>
#include <stdlib.h>//包含qsort函数的头文件int com_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void print_arr(int arr[], int sz)
{int i = 0;for (i = 0; i < sz; ++i){printf("%d ", arr[i]);}
}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], com_int);print_arr(arr, sz);
}int main()
{test1();return 0;
}

#include <stdio.h>
#include <stdlib.h>//测试qsort排序结构体数据
struct Stu
{char name[20];int age;
};
//结构体比较大小可以按照年龄比,也可以按照名字比较
com_stu_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}void test2()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 12} };int sz = sizeof arr / sizeof arr[0];qsort(arr, sz, sizeof arr[0], com_stu_by_age);
}int main()
{test2();return 0;
}

        排序之前是按照数组初始化的顺序,再看排序后的->

#include <stdio.h>
#include <stdlib.h>
#include <string.h>struct Stu
{char name[20];int age;
};
//结构体按照名字比较
int com_stu_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}void test3()
{struct Stu arr[] = { {"zhangsan", 20}, {"lisi", 30}, {"wangwu", 12} };int sz = sizeof arr / sizeof arr[0];qsort(arr, sz, sizeof arr[0], com_stu_by_name);
}int main()
{test3();return 0;
}

         排序之后的数组->

         qsort之所以能实现这种功能,是因为抽象出了比较方法(函数),你把比较方法传进来就能进行比较了。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建灵活、高效的HTTP/1.1应用:探索h11库

文章目录 构建灵活、高效的HTTP/1.1应用&#xff1a;探索h11库背景这个库是什么&#xff1f;如何安装这个库&#xff1f;库函数使用方法使用场景常见的Bug及解决方案总结 构建灵活、高效的HTTP/1.1应用&#xff1a;探索h11库 背景 在现代网络应用中&#xff0c;HTTP协议是基础…

基于语音信号的说话人识别

基于语音信号的说话人识别 摘 要 语音是人类相互交流和通信最方便快捷的手段。如何高效地实现语音传输存储或通过 语音实现人机交互&#xff0c;是语音信号处理领域中的重要研究课题。语音信号处理涉及数字信号处理、语音学、语言学、生理学、心理学、计算机科学以及模式识别…

车载软件架构 --- 智能汽车软件

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所有人的看法和评价都是暂时的&#xff0c;只有自己的经历是伴随一生的&#xff0c;几乎所有的担忧和畏惧…

实际案例说明用基于FPGA的原型来测试、验证和确认IP——如何做到鱼与熊掌兼得?

作者&#xff1a;Philipp Jacobsohn&#xff0c;SmartDV首席应用工程师 Sunil Kumar&#xff0c;SmartDV FPGA设计总监 本系列文章从数字芯片设计项目技术总监的角度出发&#xff0c;介绍了如何将芯片的产品定义与设计和验证规划进行结合&#xff0c;详细讲述了在FPGA上使用硅…

UiPath调用Python脚本的完整示例

一、主要步骤&#xff1a; 1、创建Python脚本 2、安装UiPath.Python.Activities库 3、使用方法&#xff1a; a、添加python作用域 b、加载python脚本 c、调用python方法 d、获取python对象 e、显示Python结果的消息对话框 二、详细步骤 1、安装UiPath.Python.Activities库 …

【简易进度条的实现】

独夜无伴守灯下&#xff0c;清风对面吹............................................................................................. 文章目录 前言 一、【行缓冲区的引入】 1、【问题提出】 2、【\r和\n】 3、【简易倒计时程序】 二、【简易进度条的实现】 process_bar.…

【已解决】cra 配置路径别名 @ 后,出现 ts 报错:找不到模块“@/App”或其相应的类型声明。ts(2307)

cra 配置路径别名 后&#xff0c;出现 ts 报错&#xff1a;找不到模块“/App”或其相应的类型声明。ts(2307) 然后可以在 tsconfig.json 中配置 baseUrl 和 paths &#xff1a; {"compilerOptions": {"target": "es5","lib": [&quo…

es拼音分词器(仅供自己参考)

github地址&#xff1a;https://github.com/infinilabs/analysis-pinyin&#xff08;各种版本&#xff0c;对接es版本&#xff09; 拼音分词器存在的问题&#xff1a; 1、是直接将每个字的拼音返回和一段话的拼音首字母返回&#xff0c;不能很好的分词。 2、不会保留中文&am…

为什么大家都在学数字孪生呢?

随着物联网&#xff0c;大数据、人工智能等技术的发展&#xff0c;新一代信息技术与制造业正在深度融合&#xff0c;人们与物理世界的交互方式正在发生转折性的变化。数字化转型正在成为企业的重要战略&#xff0c;而数字孪生则成为全新的焦点。 当下&#xff0c;在数字技术和…

【英特尔IA-32架构软件开发者开发手册第3卷:系统编程指南】2001年版翻译,2-11

文件下载与邀请翻译者 学习英特尔开发手册&#xff0c;最好手里这个手册文件。原版是PDF文件。点击下方链接了解下载方法。 讲解下载英特尔开发手册的文章 翻译英特尔开发手册&#xff0c;会是一件耗时费力的工作。如果有愿意和我一起来做这件事的&#xff0c;那么&#xff…

LLM Observability: Azure OpenAI (一)

作者&#xff1a;来自 Elastic Vinay Chandrasekhar•Andres Rodriguez 我们很高兴地宣布 Azure OpenAI 集成现已全面上市&#xff0c;它提供了对 Azure OpenAI 服务性能和使用的全面可观察性&#xff01;另请参阅本博客的第 2 部分 虽然我们已经提供了对 LLM 环境的可视性一段…

HTML 基础标签——表格标签<table>

文章目录 1. `<table>` 标签:定义表格2. `<tr>` 标签:定义表格行3. `<th>` 标签:定义表头单元格4. `<td>` 标签:定义表格单元格5. `<caption>` 标签:为表格添加标题6. `<thead>` 标签:定义表格头部7. `<tbody>` 标签:定义表格…

第7章 内容共享

第 7 章 内容共享 bilibili学习地址 github代码地址 本章介绍Android不同应用之间共享内容的具体方式&#xff0c;主要包括&#xff1a;如何利用内容组件在应用之间共享数据&#xff0c;如何使用内容组件获取系统的通讯信息&#xff0c;如何借助文件提供器在应用之间共享文件…

基于 Python 的 Django 框架开发的电影推荐系统

项目简介&#xff1a;本项目是基于 Python 的 Django 框架开发的电影推荐系统&#xff0c;主要功能包括&#xff1a; 电影信息爬取&#xff1a;获取并更新电影数据。数据展示&#xff1a;提供电影数据的列表展示。推荐系统&#xff1a;基于协同过滤算法实现个性化推荐。用户系…

【高等数学】3-2多元函数积分学

1. 二重积分 可以想象你有一块不规则的平面薄板,它在一个平面区域上。二重积分就是用来求这个薄板的质量(假设薄板的面密度函数是)。 把区域划分成许多非常小的小方块(类似于把一块地划分成很多小格子),在每个小方块上,密度近似看成是一个常数,然后把每个小方块的质量加…

喜欢央卫 5.5.5 | 老年人专用电视直播APP

喜欢央卫是一款专门为老年人设计的电视直播APP。这款APP的名字非常简单直白&#xff0c;内容也符合老年人的口味。它提供了常用的央卫频道&#xff0c;还有V4和V6的不同线路&#xff0c;同时支持超多地方频道。界面简洁易用&#xff0c;非常适合教老人如何看电视。 大小&#…

DAY17|二叉树Part03|LeetCode: 654.最大二叉树 、617.合并二叉树 、700.二叉搜索树中的搜索、98.验证二叉搜索树

目录 LeetCode: 654.最大二叉树 基本思路 C代码 LeetCode: 617.合并二叉树 基本思路 C代码 LeetCode: 700.二叉搜索树中的搜索 基本思路 C代码 LeetCode: 98.验证二叉搜索树 中序遍历判断递增 基本思路 C代码 递归法 C代码 LeetCode: 654.最大二叉树 力扣…

《数字图像处理基础》学习05-数字图像的灰度直方图

目录 一&#xff0c;数字图像的数值描述 &#xff11;&#xff0c;二值图像 &#xff12;&#xff0c;灰度图像 3&#xff0c;彩色图像 二&#xff0c;数字图像的灰度直方图 一&#xff0c;数字图像的数值描述 在之前的学习中&#xff0c;我知道了图像都是二维信息&…

golang的多表联合orm

项目截图 1.数据库连接配置 DbConfigUtil.go package configimport ( "fmt" _ "github.com/go-sql-driver/mysql" "gorm.io/driver/mysql" "gorm.io/gorm" "gorm.io/gorm/logger" "gorm.io/gorm/schema" )var Go…

Chromium 中chrome.topSites扩展接口定义c++

一、chrome.topSites 使用 chrome.topSites API 访问新标签页上显示的热门网站&#xff08;即最常访问的网站&#xff09;。不包括用户自定义的快捷方式。 权限 topSites 您必须声明“topSites”扩展程序清单中授予使用此 API 的权限。 {"name": "My exten…