C语言快速入门之指针详解

news2024/11/18 2:55:38

一.指针基础

1.指针定义的理解

就像我们住房子划分房间一样,在系统中,内存会被划分为多个内存单元,一个内存单元大小是一个字节,我们在搜索房间时有门牌号,与之类似,每个内存单元都会有一个编号 = 地址 = 指针,计算机中把内存的编号叫地址,C语言中给地址起了新名字指针

2.变量的创建和指针的指向

变量创建的本质是在内存中开辟一块空间

int main() {
	int a = 10;	//变量创建的本质是在内存中开辟一块空间
	//分配了四个字节,取地址是第一个字节的地址(较小的)
	printf("%p",&a);		//&取地址操作符	%p专门打印地址的(十六进制的形式打印的)
	//printf("%X", &a);
	return 0;
}

在上面,我们定义了一个int类型的变量a,而当a创建时,就要为a分配空间,int型分配四个字节,我们使用&(取地址符号)获得的就是a的第一个字节(最小的字节)的地址

假设内存中这样存放a,我们取地址取出的就是0x0012ff40

3.指针的书写和含义

那么我们将a的地址取出来后,怎么把它放到一个变量里呢?

我们这样来书写:

int a = 10;
int* pa = &a;

int * pa中,*代表pa是指针变量,int代表该指针指向的类型是什么(a的类型)

那么,既然指针也是一个变量,那么指针变量的大小是多少?

4.指针变量的大小

指针变量需要多大的空间,取决于存放的是什么(地址),地址的存放需要多大的空间,指针变量的大小就是多大(与指针指向的内容的类型无关)

我们可以写出如下代码来观察指针的内存

int main() {
	int a = 20;
	char ch = 'w';
	char* pc = &ch;
	int* pa = &a;
	printf("%d\n", sizeof(pc));
	printf("%d\n", sizeof(int*));
	return 0;
}

分开写结果如下:

5.指针类型的意义

我们已经知道了*代表这是一个指针变量,那么前面指向的类型又有什么作用呢?

事实上,指针类型决定了指针在进行解引用的时候访问多大的空间,例如,我们在进行解引用操作时,int*解引用访问4个字节,char*解引用访问1个字节

分析图如下:

当我们使用char *去指向int类型时,解引用只会访问一个字节的内容,而我们知道int类型是四个字节,所以出现了错误

6.指针相加减

指针类型决定了指针的步长,向前或者向后走一步大概的距离

int main() {
	int a = 10;
	int* pa = &a;
	printf("%p\n", pa);
	printf("%p\n", pa + 1);
	char* pc = &a;
	printf("%p\n", pc);
	printf("%p\n", pc + 1);
	return 0;
}

对于int*类型的指针变量,+1会移动四个字节,char*类型的指针变量,+1会移动一个字节,因此,我们要根据实际的需要,选择适当的指针类型才能达到效果

7.void*指针

无具体类型的指针(范性指针)可以接受任意类型的地址,不能进行解引用的操作,和指针的+-操作

int main() {
	int a = 10;
	int* pa= &a;
	void* pc = &a;
	char b;
	void* pd = &b;
	return 0;
}

8.const和指针

我们知道,const表示常属性,不能被修改,例如:

int main() {
	const int n = 10;	//n是变量,但不可以改变,常变量
				//如果不想让n变,就在前加const
	printf("%d", n);
	return 0;
}

此时,n是无法改变的

但是我们可以使用指针来更改数字,如下:

int main() {
	const int n = 10;
	int* p= &n;
	*p = 20;
	printf("%d", n);
	return 0;
}

这样,我们就可以改变n的变量

const修饰指针有两种情况,分为const在*前和const在*后,

1.const放在*的左边,修饰的是*p        const int* p

2.const放在*的右边,修饰的是p        int* const p

两者有什么区别呢?

int main() {
	const int n = 10;
	int m = 100;
		//const int(* p) = &n;
		*p = 20;		//不能改变对象的值了
		// p = &m;		//可以改变指向的对象
		//int* const p = &n;
		p = &m;		//不可以改变指向的对象
		//*p = 20;		//可以改变对象的值
	//p是指针变量,里面存放了其他变量的地址
	//p是指针变量,*p(n)是指针指向的对象
	//p是指针变量,p也有自己的地址&p
	return 0;
}

const放在*前,会管整个(*p),使其不能改变值,但是p不受管理

const放在*后,只管p,不能改变指向的对象,但是可以改变*p

9.指针的运算

指针的基本运算分为三种:
指针+-整数
指针 - 指针
指针的关系运算

指针加减整数运算

int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
		//数组在内存中连续存放,随着下标增长,地址由低到高变化的
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	for (i = 0; i < sz; i++) {
		printf("%d\n", *p);
		p++;	//p = p + 1
	}
	return 0;
}

指针-指针 == 指针之间元素的个数(运算的前提条件是两个指针指向了同一块的空间)

int main() {
	int arr[10] = { 0 };
	printf("%d", &arr[9] - &arr[0]);
	printf("%d", &arr[0] - &arr[9]);
	//指针 - 指针的绝对值是指针和指针之间的元素个数

	return 0;
}

指针的运算关系:两个指针比较大小

int main() {
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int* p = &arr[0];
	while (p < arr + sz) {
		printf("%d\n", *p);
		p++;
	}
	return 0;
}

10.野指针

野指针    指针指向的位置是不可知的(随机的,不正确的,没有限制的)很危险

野指针的成因:

//成因一:指针未初始化
int main()
{
	int* p;		//局部变量不初始化的时候,他的值是随机值
	*p = 20;
	printf("%d\n", *p);
	return 0;
}
//成因二:指针的越界访问
int main() {
	int arr[10] = { 0 };
	int* p = arr[0];
	int i = 0;
	for (i = 0; i < 11; i++) {		//指向了不属于的空间,且用指针访问了
		*(p++) = i;
	}
	return 0;
}
//成因三:指针空间释放
int* test() {
	int n = 100;
	return &n;
}
int main() {
	int* p = test();	//p存n的地址,但是空间回收了
	printf("%d", *p);
	return 0;
}

如何规避野指针

1.指针初始化    知道就直接赋地址,不知道就赋值NULL
2.小心指针越界
3.指针不在使用时,及时置为NULL,指针使用前检查有效性
4.避免返回局部变量的地址

int main() 
{
	//int a = 10;
	//int* pa = &a;
	//*pa = 20;
	//printf("%d", a);

	//int* p = NULL;	//空指针,地址是0,不能赋值
	//*p //err

	int a = 10;
	int* p = &a;
	if (p != NULL) //检测指针有效性
	{
		*p = 20;
	}

	return 0;
}

那是否我们可以更简单的方法来检测指针的有效性呢?

11.assert断言

assert的作用:运行时确保程序符合指定条件,不合条件,报错并终止运行

形式如下        assert(表达式);        表达式如果不成立则会报错

当然,assert需要头文件

#include	<assert.h>

上面的代码可以更简单的写成下面的形式

int main()
{
	int a = 10;
	int* p = NULL;
	assert(p != NULL);	//err
	*p = 20;
	printf("%d\n", *p);

	return 0;
}

在这里,由于*p是NULL,直接报错并终止运行了

如果后面我们不再需要assert断言,我们不需要把所有写出的assert代码都手动删除,只需要在头文件位置写成:

#define	NDEBUG
#include	<assert.h>

即可使得assert失效

12.指针的使用和传址调用

(1)我们尝试用代码实现strlen()

size_t Mystrlen(const char* s)
{
	size_t count = 0;
	assert(s != NULL);
	while (*s != '\0') {
		s++;
		count++;
	}
	return count;
}

int main() 
{
	//strlen()-求字符串长度
	char arr[] = "abcdef";
	size_t len = Mystrlen(arr);	//字符串中\0前的字符个数
	printf("%zd\n", len);

	return 0;
}

(2)函数实现两个数的交换

void swap(int x, int y) {
	int z = 0;
	z = x;
	x = y;
	y = z;
	return 0;
}

int main() 
{
	int a = 10;
	int b = 20;
	printf("%d %d\n", a, b);
	swap(a, b);	
	printf("%d %d\n", a, b);


	return 0;
}

我们写出代码发现a与b的值没有发生改变,实际上,实参变量传给形参时候,形参是一份临时拷贝,对形参的修改不会影响实参,因此,我们应该传地址过去,实现改变

void swap(int* x, int* y) {
	int z = 0;
	z = *x;
	*x = *y;
	*y = z;
	return 0;
}

int main() 
{
	int a = 10;
	int b = 20;
	printf("%d %d\n", a, b);
	swap(&a, &b);	//传址调用
	printf("%d %d\n", a, b);


	return 0;
}

二.指针和数组

1.数组名的理解

在大部分情况下,数组名是数组首元素的地址,有两个例外:sizeof数组名,这里表示整个数组的大小,&数组名,这里的数组名也表示整个数组,取出整个数组地址

int main() 
{	
	int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%p\n", arr);
	printf("%p\n", arr + 1);	//跳过了一个元素
	printf("%p\n", &arr[0] + 1);	//跳过了一个元素
	printf("%p\n", &arr + 1);	//跳过了一个数组
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr[0]));

	return 0;
}

2.指针表示数组

我们知道了,数组名实际上是数组第一个元素的地址,因此,指针表示数组可以写成

arr[i] == *(arr+i) == *(p + i) == p[i]

我们输出数组元素可以使用指针的表示:

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]);
	int i = 0;
	//for (i=0; i < sz; i++) {
	//	scanf("%d", p + i);
	//}
	for (i=0; i<sz; i++) {
		printf("%d ", *(p+i));
	}

	return 0;
}

3.一维数组传参的本质

一维数组传参传过去的是数组首元素的地址

void test(int arr[])	//本质上arr是指针
{
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz2);
}

int main()
{
	int arr[10] = { 0 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz1);

	test(arr);

	return 0;
}

4.冒泡排序法

冒泡排序的思想

1.两两相邻的元素进行比较

2.如果不满足顺序就交换,满足顺序就找下一对

例如,现在给出我们一个数组,让我们将数组由小到大(升序)排列

过程大致如下

从头开始进行冒泡排序,一趟冒泡排序会排好最后一个的内容,我们如果需要排n个元素,最多需要进行n-1次,而且由于每次都可以排好一个,因此每次冒泡需要的次数越来越少

我们写出以下的代码实现了冒泡排序:

void bubble_sort(int arr[],int sz) 
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int flag = 1;
		int j = 0;
		for(j=0; j<sz-1-i; j++)
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
				flag = 0;
			}
		if (flag == 1)
		{
			break;
		}
	}
}

void print_arr(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1 };
	//实现排序,排成升序
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr,sz);
	print_arr(arr, sz);
	return 0;
}

当然,我们所说的进行n-1次冒泡排序是我们考虑的最坏的结果,很多时候,在我们没有进行到n-1次时排序就已经完成,排序完成的标志是本次排序中没有进行过一次交换,因此,我们可以加入判断是否交换过来提前结束本次的排序

我们写出了如下函数:

void bubble_sort2(int arr[], int sz)
{
	int i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		int j = 0;
		for (j = 0; j < sz - 1 - i; j++)
			if (*(arr+j) > *(arr+j+1))
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
	}
}

二级指针

一级指针指向的是其他变量,而二级指针是存放一级指针地址的

int main()
{
	int a = 10;
	int* p = &a;
	int** pp = &p;
	**pp = 20;
	printf("%d\n", a);

	return 0;
}

5.指针数组

指针数组是存放指针的数组,是一个数组,数组内容是指针

	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[3] = {arr1, arr2, arr3};

其中int * arr[3]就是一个指针数组

我们可以使用指针数组来模拟实现二维数组

这样使用了指针数组来实现了二维数组的访问

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* arr[3] = {arr1, arr2, arr3};

	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.字符指针-存放字符的指针

字符指针是一个指针,指向的内容是字符/字符串

int main()
{
	char ch = 'w';
	char* pc = &ch;
	printf("%c\n", *pc);
	*pc = 'q';
	printf("%c\n", ch);

	return 0;
}

也可以拿字符指针指向字符串,代码如下

int main()
{
	char* p = "hello world";	
	return 0;
}

但是,对于上面的代码,我们如果尝试修改指针的内容:

int main()
{
	const char* p = "hello world";		
	* p = "hi world";
	return 0;
}

发现会报错,实际上,我们可以把字符串理解成字符数组,但并不完全是,区别是:数组是可以修改的,而这里的p指向的是常量字符串,不能修改,但是可以使用

int main()
{
	const char* p = "hello world";
	printf("%s\n", p);
	printf("%s\n", "hello world");
	
	int len = strlen(p);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		printf("%c",*(p + i));
	}

	return 0;
}

这样我们就可以使用字符指针输出字符串啦。

2.数组指针

数组指针是指向数组的指针,是指针,存放的是数组

int main()
{
	int arr[10] = { 0 };
	int (*p)[10] = &arr;//取出的是数组的地址	数组指针
//	int *p[10]; 指针数组

	return 0;
}

请注意数组指针和指针数组的区别,这里写出了一些代码:

int main()
{
	char arr[5];
	char (*p1)[5] = &arr;	//char (*)[5]是数组指针类型
	//5不能省略掉
	char* p2 = arr;
	char* p3 = &arr[0];

	return 0;
}

char (*p1)[5] = &arr;定义了一个数组指针

    char* p2 = arr;
    char* p3 = &arr[0];定义的都是指针,指针指向的是arr数组

二维指针传参时候,二维数组的数组名是第一行的地址

我们在平时输出二维数组时,代码是这样的:

void test(int arr[3][5],int r,int c)
{
	int i = 0;
	for (i = 0; i < r; i++)
	{
		int j = 0;
		for (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} };
	test(arr, 3, 5);
	return 0;
}

我们可以利用数组指针来实现二维变量

void test_(int(*p)[5], int r, int c)
{
	int x = 0;
	for (x = 0; x < r; x++)
	{	
		int y = 0;
		for (y = 0; y < c; y++)
		{
			printf("%d ", (*(p+x))[y]);
		}
		printf("\n");
	}
}


int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	test(arr, 3, 5);
	test_(&arr, 3, 5);

	return 0;
}

3.函数指针

函数指针是一个指针,指向的是函数,存放的是函数的地址

int Add(int a, int b)
{
	return a + b;
}

int* test(char* s)
{
	return NULL;
}

int main()
{
	int (*pf)(int, int) = Add;
	int* (*ph)(char* s) = test;
	//int x = 10;
	//int y = 20;
	//int z = Add(x, y);
	printf("%p\n", &Add);
	printf("%p\n", Add);

	//&函数名和函数名都表示函数的地址

	return 0;
}

函数指针变量的写法和数组指针变量的写法类似

4.typedef重命名

typedef是用来类型重命名的,可以将复杂的类型简单化

typedef	unsigned int uint;

int main()
{
	unsigned int num1;
	uint num2;

	return 0;
}

在这里我们对指针类型进行重命名

typedef int* pint;

int main()
{
	int i = 10;
	int* p1 = &i;
	pint p2 = &i;
	
	return 0;
}

typedef和define的区别

我们知道两者都可以定义全局变量,两者的区别如下

typedef int* ptr_t;
#define PTR_T int*

int main()
{
	ptr_t p1, p2;//p1,p2是整型指针
	PTR_T p3, p4;//int *p3,p4
				//p3是指针,p4是整型
}

5.函数指针数组

如果把多个相同类型的函数指针存放在一个数组中,这个数组就是函数指针数组

我们写出如下的函数:

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

我们定义函数指针这样来定义,我们发现他们的输入类型和输出类型都是相同的

int main()
{
	int (*pf1)(int, int) = Add;
	int (*pf2)(int, int) = Sub;
	int (*pf3)(int, int) = Mul;
	int (*pf4)(int, int) = Div;
	
	return 0;
}

因此,我们可以创建一个函数指针数组,代码如下:

int main()
{
	
	int (*pfarr[4])(int, int) = {Add, Sub, Mul, Div};//pfArr就是函数指针
	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int ret =pfarr[i](8, 4);
		printf("%d\n", ret);
	}

	return 0;
}

这样我们就可以更简便的定义啦

如果让我们编写一个整型的加减乘除计算器,我们还可以写出一个函数

void Calc(int (*pf)(int, int))
{
	int x = 0;
	int y = 0;
	int ret = 0;
	printf("请输入两个操作数");
	scanf("%d %d", &x, &y);
	ret = pf(x, y);
	printf("%d", ret);
}
void menu()
{
	printf("**********************\n");
	printf("*****1.add  2.sub*****\n");
	printf("*****3.mul  4.div*****\n");
	printf("*****4.exit***********\n");
	printf("**********************\n");

}

int main()
{
	int input = 0;
	do
	{
		menu();
		printf("请输入");
		scanf("%d", &input);
		switch(input)
		{
			case 1:
				Calc(Add);
				break;
			case 2:
				Calc(Sub);

				break;
			case 3:
				Calc(Mul);

				break;
			case 4:
				Calc(Div);

				break;
			case 0:
				printf("退出计算器");
				break;
			default:
				printf("选择错误");
				break;
		}
	} while (input);
	return 0;
}

这样就可以了

但我们发现,如果我们运用switch还是很麻烦,因此我们创建一个函数指针数组

int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	//创建一个函数指针的数组
	int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div };
							   //	 0	  1   2   3    4
	do
	{
		menu();
		printf("请输入");
		scanf("%d", &input);
		if (input >= 1 && input <= 4)
		{
			printf("请输入两个操作数");
			scanf("%d %d", &x, &y);
			ret = pfArr[input](x, y);
			printf("%d\n", ret);
		}
		else if (input == 0)
		{
			printf("退出");
			break;
		}
		else
		{
			printf("选择错误,重新选择");
		}
	} while (input);
	return 0;
}

这样就可以使得代码更加的优秀了

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

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

相关文章

UCSF DOCK 分子对接详细案例(05)- 遗传算法用于分子生成 DOCK_GA

欢迎浏览我的CSND博客&#xff01; Blockbuater_drug …点击进入 文章目录 前言一、软件及操作环境二、遗传算法三、结构文件准备四、 DOCK_GA4.1 Fragment Library Generation4.2 运行 GA没有RDKit编译的情况RDKit编译的情况在服务器上运行 总结参考资料 前言 本文是UCSF DOC…

能源大数据采集,为您提供专业数据采集服务

随着经济的不断发展&#xff0c;能源产业也逐渐成为国民经济的支柱产业之一。而对于能源行业来说&#xff0c;数据采集是一项至关重要的工作。以往&#xff0c;能源企业采集数据主要依靠人工收集、整理&#xff0c;但是这种方式不仅效率低下&#xff0c;而且容易出现数据不准确…

Spring——Bean的作用域

bean的作用域 Bean Scope Scope说明singleton&#xff08;默认情况下&#xff09;为每个Spring IoC容器将单个Bean定义的Scope扩大到单个对象实例。prototype将单个Bean定义的Scope扩大到任何数量的对象实例。session将单个Bean定义的Scope扩大到一个HTTP Session 的生命周期…

Python 面向对象编程——类的使用

一、学习目标 1&#xff0e;掌握类的定义和实例化对象。 2&#xff0e;熟练掌握类的构造函数__init__使用。 3&#xff0e;掌握类的继承机制和使用。 二、相关练习 1、定义一个玩具类Toy()&#xff0c;创建名字为“小汽车”、“手枪”和“积木”的玩具实例&#xff0c;计…

qt cmake添加resource文件

文章目录 方式一:方式二:qrc的使用 两种方式 方式一: 创建一个qrc文件&#xff0c;在qt_add_executable 中直接添加 qt_add_executable(helloworldmain.cppimageresources.qrc )方式二: 使用 qt_add_resources qt_add_resources(helloworld "app_images"PREFIX &…

tomcat nginx 动静分离

实验目的:当访问静态资源的时候&#xff0c;nginx自己处理 当访问动态资源的时候&#xff0c;转给tomcat处理 第一步 关闭防火墙 关闭防护 代理服务器操作&#xff1a; 用yum安装nginx tomcat &#xff08;centos 3&#xff09;下载 跟tomcat&#xff08;centos 4&#xff0…

循环队列:一道使数据结构萌新知道什么是“愁滋味“的题目

这破题目肝了我一天半才搞明白,也正是因为这道题目,我才豁然明白了李煜所说的"剪不断,理还乱...别是一般滋味在心头"到底是什么"滋味".在完全搞明白之前,真的是放有放不下,理也理不清... 但是理解之后你会发现,嘛い---,也就那么个回事嘛O(∩_∩)O 目录 1…

瑞_Redis_短信登录(一)

文章目录 项目介绍1 项目准备1.1 导入SQL1.2 导入后端项目1.2 导入前端项目 &#x1f64a; 前言&#xff1a;本文章为瑞_系列专栏之《Redis》的实战篇的短信登录章节的项目准备小节。由于博主是从B站黑马程序员的《Redis》学习其相关知识&#xff0c;所以本系列专栏主要是针对该…

Tonka Finance,BTCFi 浪潮的发动机

在 2023 年年初&#xff0c;Ordinals 技术方案为比特币 Layer1 带来了一种全新的资产发行方式&#xff0c;此后一场以比特币生态为主战场的新一轮资金、注意力价值争夺战打响&#xff0c;并且越来越多的加密原教旨主义者、密码极客们加入这场战争中。我们看到&#xff0c;铭文市…

【操作系统概念】 第1章:导论

文章目录 关于本书内容0.前言1.1操作系统的功能1.1.1 用户视角1.1.2 系统视角1.1.3 操作系统的定义 1.2 计算机系统的组成1.2.1 计算机系统的运行1.2.2 存储结构1.2.3 I/O结构 1.3 计算机系统体系结构1.4 操作系统的结构1.5 操作系统的执行1.5.1 双重模式与多重模式的执行1.5.2…

DolphinScheduler——介绍及架构设计

目录 一、DolphinScheduler介绍 1.1 概述 1.2 特性 1.2.1 简单易用 1.2.2 丰富的使用场景 1.2.3 High Reliability 1.2.4 High Scalability 1.3 名词解释 1.3.1 名词解释 1.3.2 模块介绍 二、DolphinScheduler架构原理 2.1 系统架构图 2.2 架构说明 2.2.1 Maste…

【AI视野·今日Sound 声学论文速览 第五十一期】Mon, 4 Mar 2024

AI视野今日CS.Sound 声学论文速览 Mon, 4 Mar 2024 Totally 6 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Sound Papers VoxGenesis: Unsupervised Discovery of Latent Speaker Manifold for Speech Synthesis Authors Weiwei Lin, Chenhang He, Man Wai Mak, …

66-ES6:var,let,const,函数的声明方式,函数参数,剩余函数,延展操作符,严格模式

1.JavaScript语言的执行流程 编译阶段&#xff1a;构建执行函数&#xff1b;执行阶段&#xff1a;代码依次执行 2.代码块&#xff1a;{ } 3.变量声明方式var 有声明提升&#xff0c;允许重复声明&#xff0c;声明函数级作用域 访问&#xff1a;声明后访问都是正常的&…

最新LangChain+GLM4开发AI应用程序系列(三):RAG检索增强生成篇

最新LangChainGLM4开发AI应用程序系列&#xff08;三&#xff09;&#xff1a;RAG检索增强生成篇 一、前言二、RAG介绍1、文档加载器2、文本分割器3、嵌入模型4、向量数据库 三、RAG开发案例1、创建智谱GLM4大模型对象2、加载文档3、文本分割4、向量化存储5、向量库检索6、生成…

初始网络 --- 网络基础

目录 0、 前言 1、 计算机网络发展背景 1.1. 局域网(LAN) && 广域网(WAN) 2、 认识并理解协议 3、 初始网络协议 3.1. 协议分层 4、 TCP/IP 五层(或四层)模型 4.1. 简单了解TCP/IP层状体系 4.2. TCP/IP协议层状结构和计算机层状结构的关系 5、 OSI七层模型 …

程序员如何选择职业赛道:探索未知,寻找激情

作为程序员&#xff0c;我们时常面临职业选择的难题。在这个充满变革的行业中&#xff0c;如何选择适合自己的职业赛道成为了我们关注的焦点。本文将探讨程序员如何选择职业赛道&#xff0c;帮助你找到适合自己的发展方向。 一、认识自己的兴趣和激情 首先&#xff0c;我们需要…

为什么要用云手机进行国外社媒监控?

随着全球化的不断发展&#xff0c;社交媒体已成为企业推动全球品牌知名度和业务流量的关键渠道。在这个数字时代&#xff0c;云手机作为一种强大的工具&#xff0c;为国外社交媒体监控提供了全新的可能性。在这篇文章中&#xff0c;我们将探讨使用云手机进行国外社媒监控的重要…

RHCSA练习2

一、实验题目 1、文件查找 &#xff08;1&#xff09;在当前目录及子目录中&#xff0c;查找小写字母开头的txt文件 [rootroot ~]# cd /etc [rootroot etc]# find . -type f -name [a-z]*.txt &#xff08;2&#xff09;在/etc及其子目录中&#xff0c;查找host开头的文件 …

Java解决杨辉三角

Java解决杨辉三角 01 题目 给定一个非负整数 *numRows&#xff0c;*生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2: 输入: numRo…

人工智能-飞桨

文章目录 概要安装零基础教程基础知识小结 概要 集核心框架、基础模型库、端到端开发套件、丰富的工具组件于一体的深度学习平台 官方入口 安装 python安装 python官方下载 PaddlePaddle安装 python -m pip install paddlepaddle2.6.0 -i https://mirror.baidu.com/pypi/s…