C进阶_C语言_函数与指针_C语言指针进阶

news2024/12/25 9:15:04

上一篇博客http://t.csdn.cn/GYCiM

我们了解了指针相关知识,今天来了解函数和指针的关系。

目录

函数指针

函数指针数组

指向函数指针数组的指针

回调函数

qsort

冒泡排序模拟实现qsort


函数指针

我们知道,数组指针是指向数组的指针。

int arr[10]={0};
int (*p)[10]=&arr;

那么函数指针就是指向函数的指针。

假设有函数Add……

int Add(int x,int y)
{
    return x+y;
}

那么现在取它的地址,看看能不能打印出一个地址:

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

运行程序,果然是地址:

既然是指针就可以存起来,那么函数指针该怎么存呢?

首先应该明确一个指针。所以在定义指针变量时应用括号把变量名括起来,再去明确参数。

如果想得到含数调用后的结果,就要用一个变量来接收调用后的数据:

int (*pf)(int, int) = &Add;
int ret = (*pf)(2, 3);

要注意&Add和Add是一个效果,都是函数的地址。

用以下方式也可以实现,并且在调用函数时可以直接用pf。

int (*pf)(int, int) = Add;
int ret = pf(2, 3);

调用函数时pf前面的*可以说就是摆设,有无都可以。

因为平时在调用函数时,直接以Add()的方式调用即可,而Add又是地址,而函数指针里存放的又是地址,所以我们在用函数指针调用函数时可以不去加*。

但是注意,要写*的话,就一定要把*和pf一起用括号括起来。

阅读两段有趣的代码:

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

怎么读懂代码1?

我们从0下手。0前面的括号里边括起来的是void (*)()这是一个函数指针类型。

既然void (*)()是被放在括号里的,说明有进行类型强制转换,转换的是谁呢?是0。

0本来是int型的,现在把它强制转换成函数指针类型。

0被当成了一个函数的地址。

void (*)()前边加了*,说明在进行解引用操作。

事实上解引用操作可有可无。

要注意这个函数没有参数所以最后边是()。没有传参。

总而言之就是把0以函数的形式调用了。该代码是一次函数调用。

再次解释一遍:

该代码是一次函数调用,

调用0地址处的一个函数,

首先代码中将0强制类型转换为类型为void (*)()的函数指针,

然后去调用0地址处的函数。

那代码2如何解读呢?

从signal下手。很显然signal是一个函数,它的参数分别为int变量和一个参数为int的void型函数指针。

signal在调用后很可能会返回一个参数为int的void型函数指针,然后再调用此函数。

再次解读:

该代码是一次函数的声明,

声明的函数名字叫signal,

signal函数的参数有2个,第一个是int类型,第二个是函数指针类型,该函数指针能够指向的那个函数的参数是int,

返回类型是void,

signal函数的返回类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回类型是void。

因为signal内外都有void(*)(int),所以这个代码可以被简化:

typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);

函数指针数组

我们已经学了以下知识:

int my_strlen(const char* str)
{
    return 0;
}
int main()
{
	//指针数组
	char* ch[5];
	int arr[10] = {0};
	//pa是数组指针
	int (*pa)[10] = &arr;
	//pf是函数指针
	int (*pf)(const char*) = &my_strlen;
	return 0;
}

那要把函数的地址存到一个数组中,那这个数组就叫函数指针数组,那函数指针的数组如何定义呢?

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

答案是:parr1。
parr1先和[]结合,说明parr是数组,数组的内容是什么呢?
是int (*)()类型的函数指针。

//函数指针数组
int (*pfA[5])(const char*) = { &my_strlen};

下面来实现一个计算器:

#include <stdio.h>
void menu()
{
	printf("*******************************\n");
	printf("****** 1. add   2. sub    *****\n");
	printf("****** 3. mul   4. div    *****\n");
	printf("****** 0. exit            *****\n");
	printf("*******************************\n");
}
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 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("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);
	return 0;
}

如果想把功能拓展下,增加更多的功能,那么该怎么写代码?

那么我们就要再写多个函数,然后再switch中不断增加case break。

我们发现这些函数的参数、类型一模一样:

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 (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };

然后这样改进(函数已省略):

int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div };
int main()
{
	int input = 0;
	int x = 0;
	int y = 0;
	int ret = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		if (input == 0)
		{
			printf("退出计算器\n");
			break;
		}
		else if (input>=1 &&input<=4)
		{
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = pf[input](x, y);
			printf("%d\n", ret);
		}
		else
		{
			printf("选择错误\n");
		}
	} while (input);

	return 0;
}

如果想添加功能,只需要定义函数,然后在int (*pf[5])(int, int) = { NULL, Add, Sub, Mul, Div }中添加函数名、修改成员数即可。

指向函数指针数组的指针

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

int main()
{
	int arr[10];
	int (*pA)[10] = &arr;
	//函数指针数组
	int (* pf[5])(int, int);
	//ppf是指向函数指针数组的指针
	int (*(* ppf)[5])(int, int) = &pf;
	return 0;
}

在int (*(* ppf)[5])(int, int) = &pf里,因为ppf和[5]不结合,那么(*ppf)[5]就是数组。

前边加上了*后,*和(*ppf)[5]结合,那么ppf就是指针,是数组pf的指针。

它们是这样存放的:

p中存放的是函数指针,pf中存放的是多个函数的指针,ppf指向pf。

回调函数

函数指针的最重要的一个应用就是回调函数。

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

我们回顾下刚刚写的计算器的部分代码:

	do 
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Add(x, y);
			printf("%d\n", ret);
			break;
		case 2:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Sub(x, y);
			printf("%d\n", ret);
			break;
		case 3:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Mul(x, y);
			printf("%d\n", ret);
			break;
		case 4:
			printf("请输入两个操作数:>");
			scanf("%d %d", &x, &y);
			ret = Div(x, y);
			printf("%d\n", ret);
			break;
		case 0:
			printf("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

我们可以发现每个case后、break之前的代码逻辑是非常相似的。

可以封装一个函数,然后多次调用,并且在调用的时候把相应函数的地址传递过去:

void menu()
{
	printf("*******************************\n");
	printf("****** 1. add   2. sub    *****\n");
	printf("****** 3. mul   4. div    *****\n");
	printf("****** 0. exit            *****\n");
	printf("*******************************\n");
}

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 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\n", ret);
}

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("退出计算器\n");
			break;
		default:
			printf("选择错误\n");
			break;
		}
	} while (input);

	return 0;
}

qsort

排序算法有很多,今天要用到冒泡排序和C语言库函数自带的qsort函数。

有关冒泡排序的知识,欢迎参考我之前的博客http://t.csdn.cn/zhKV6

在MSDN中查找qsort

void qsort(
void *base, 
size_t num, 
size_t width, 
int (__cdecl *compare )(const void *elem1, const void *elem2 )
);

 这个函数有四个参数,分别是void型指针、两个int型(size_t就是int)变量和一个比较函数指针。

Parameters

base

Start of target array

num

Array size in elements

width

Element size in bytes

compare

Comparison function

elem1

Pointer to the key for the search

elem2

Pointer to the array element to be compared with the key

从MSDN文档可得知base是目标数组的起始位置,num是数组元素个数,width是数组内每个元素的大小(字节),然后是比较函数指针,两个形参分别是要比较的数。

qsort可以对任意类型的数组进行排序。

我们用冒泡排序来模拟实现下qsort,先看冒泡排序的函数:

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-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				int tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}
}

这样的代码只能对整数进行排序!因为第二层for内,if后用的是>,只能比较两个整数的大小。

而两个字符串之间的比较要用strcmp,所以此代码只能对整数进行排序。

那么针对不同的数据类型就要用到不同的函数了,qsort中最后一个参数的作用就显现出来了。

qsort的参数中:

int (__cdecl *compare )(const void *elem1, const void *elem2 )

elem1和elem2分别是要比较的两个元素的地址。

elem1和elem2两个指针是void型的指针。为什么是void?

这是因为qsort作者在设计此函数时,并不知道未来的开发者会将什么样的数据类型进行排序。

void*可以用于接收任何类型的指针,但是不能直接解引用,在使用时要进行类型转换。

void型指针也不能直接进行运算,比如,变量名++是错误的。

下边来实现一个比较整型的数组:

int cmp_int(const void* e1, const void* e2)
{
}
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);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

因为要排序的数据是int型,所以在cmp_int内部就要将e1和e2强制类型转换:

int cmp_int(const void* e1, const void* e2)
{
	return *(int*)e1 - *(int*)e2;
}

在qsort的MSDN文档中,我们看到:

Return ValueDescription
< 0elem1 less than elem2
0elem1 equivalent to elem2
> 0elem1 greater than elem2

当elem1小于elem2时返回值小于0,当elem1和elem2相等时返回值等于0,当elem1大于elem2时返回值大于0。

所以就要用第一个元素的值减去第二个元素的值,和qsort保持一致。

当返回值传递到qsort中后,qsort会根据返回值是大于0还是等于0还是小于0,再结合前三个参数进行排序。

qsort默认正序。也就是说e1-e2升序,e2-e1降序。

要注意使用qsort要包含stdlib头文件。

下面来实现结构体排序:

struct Stu
{
	char name[20];
	int age;
};
int cmp_stu_by_age(const void* e1, const void* e2)
{
	return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}
void test2()
{
	struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
	int sz = sizeof(s) / sizeof(s[0]);
	qsort(s, sz, sizeof(s[0]), cmp_stu_by_age);
}
int main()
{
    test2();
    return 0;
}

这是对学生按照年龄来排序。

也可以按照名字来排序:

int cmp_stu_by_name(const void* e1, const void* e2)
{
	return strcmp(((struct Stu*)e1)->name, ((struct Stu*)e2)->name);
}

要注意,这里用strcmp,并且要引用头文件string.h。

查阅strcmp的文档可知,strcmp的返回值和qsort返回值非常类似:

ValueRelationship of string1 to string2
< 0string1 less than string2
0string1 identical to string2
> 0string1 greater than string2

都是第一个元素大于第二个元素返回值大于0,第一个元素和第二个元素相等返回值为0,第一个返回值小于第二个元素返回值小于0。

所以可以在cmp_stu_by_name中直接返回strcmp的返回值。

对s进行监视,可以看到成功实现了排序。

冒泡排序模拟实现qsort

下面用回调函数,冒泡排序来模拟qsort。

改造冒泡排序,使冒泡排序能对任何类型的数组排序:

思想还是冒泡排序,但与以往不同的是,它可以对任意类型的数组排序。

写一个bubble_sort的函数,因为不知道将来会对什么类型的数据进行排序,那函数的待排序数组的形参就要是void类型,函数指针cmp的参数要用void*类型:

void bubble_sort(void*base, size_t sz, size_t width, int (*cmp)(const void*e1, const void*e2))
{
}

假设我们要排序的是整型数组……

函数传递的是指针,那么相邻两个元素交换时,由于是整型数组,数组的每个元素是int型,那么第一个元素的地址就要+4,第二个元素的地址就要-4

这里假设第一个元素地址是base,元素交换时加四个字节。

请思考下,哪个数据类型是1个字节?

字符型!char!一个char类型的长度1*width就是一个int型的宽度!

所以在设计比较两个元素大小的函数时,强制转换成char*类型

先看bubble_sort的参数,width是一个元素的宽度,那么可以这样写比较两个元素大小的函数的参数:

cmp((char*)base+j*width, (char*)base+(j+1)* width);

应注意cmp只是bubble_sort函数的参数。

将它的返回值作为if的条件:

if (cmp((char*)base+j*width, (char*)base+(j+1)* width)>0)
{
}

再封装一个交换函数:

void Swap(char* buf1, char*buf2, int width)
{
	int i = 0;
	for (i = 0; i < width; i++)
	{
		char tmp = *buf1;
		*buf1 = *buf2;
		*buf2 = tmp;
		buf1++;
		buf2++;
	}
}

应注意这里是一个字节一个字节地进行交换:

  这样就实现了:

void bubble_sort(void*base, size_t sz, size_t width, int (*cmp)(const void*e1, const void*e2))
{
	//趟数
	size_t i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		size_t j = 0;
		for (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);
			}
		}
	}
}

 使用我们自己写的bubble_sort函数排序整型数组,运行后发现得到了预期的结果:

void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void test3()
{
	int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
int main()
{
    test3();
	return 0;
}

我们也可以用自己实现的bubble_sort对结构体进行排序,运行后同样得到了预期的结果:

void bubble_sort(void*base, size_t sz, size_t width, int (*cmp)(const void*e1, const void*e2))
{
	//趟数
	size_t i = 0;
	for (i = 0; i < sz - 1; i++)
	{
		//一趟冒泡排序的过程
		size_t j = 0;
		for (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 test4()
{
	struct Stu s[3] = { {"zhangsan",20}, {"lisi", 50}, {"wangwu", 33} };
	int sz = sizeof(s) / sizeof(s[0]);
	bubble_sort(s, sz, sizeof(s[0]), cmp_stu_by_name);
}
int main()
{
	test4();
	return 0;
}

创作不易,码了九千多字,求各位三连支持下!

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

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

相关文章

Ribbon负载均衡服务调用

文章目录一. 什么是Ribbon二. Ribbon负载均衡三. Ribbon负载均衡策略四. Ribbon饥饿加载一. 什么是Ribbon PS: 本篇文章文作者学习笔记&#xff0c;技术参考价值不大。 Spring Cloud Ribbon是基于Netflix Ribbon实现的一套客户端&#xff0c;负载均衡的工具。 简单的说&#x…

Allegro174版本新功能介绍之改变报表字体大小

Allegro174版本新功能介绍之改变报表字体大小 Allegro在升级到174的时候,默认show element的字体是非常小的,类似下图,辨认起来非常困难 但是174是支持把字体调整的大一些的,具体操作如下 选择Setup选择User Preferences

SpringBoot整合java诊断工具Arthas

一、Arthas官方文档https://arthas.aliyun.com/doc/二、springBoot整合方式1、pom文件引入<dependency><groupId>com.taobao.arthas</groupId><artifactId>arthas-spring-boot-starter</artifactId><version>3.6.7</version> </d…

机器学习:机器学习常见的算法分类和算法优缺点汇总

机器学习实战教程(13篇)_M_Q_T的博客-CSDN博客这些网址非常适合想学习机器学习&#xff0c;却苦于没有项目&#xff08;尤其缺少数据&#xff09;的人。无意中看到&#xff0c;给自己做一个记录。目录大类&#xff1a;学习方式监督式学习&#xff1a;非监督式学习&#xff1a;半…

ES6 课程概述③

文章目录5-1. 新增的对象字面量语法5-2. Object 的新增 API5-4 类&#xff1a;构造函数的语法糖传统的构造函数的问题类的特点5-5. 类的其他书写方式5-1. 新增的对象字面量语法 成员速写 如果对象字面量初始化时&#xff0c;成员的名称来自于一个变量&#xff0c;并且和变量的…

2023/01/05 java面试题每日10问

1.What is Java? Java is object-oriented, platform-independent, Multithreaded, and portable programming language.it provides its own JRE and API. 2.What is the difference between JDK, JRE, and JVM? JVM Java Virtual Machine provides the runtime environm…

小小闭门会,揭示SaaS大趋势

从2014年中国的SaaS领域开始有融资到现在已经8年&#xff0c;从最开始的一张云图填不满到现在的密密麻麻&#xff0c;厂商和产品如雨后春笋般的多了起来&#xff0c;但很多SaaS依然在孤军奋战&#xff0c;很多厂商陷入定制化泥潭。有人说中国的SaaS有特殊国情&#xff0c;大企业…

【bioinfo】酶切法片段化建库相比超声打断建库引入softclip使用FADE软件识别/去除

FADE软件参考文献 参考文献&#xff1a;片段化酶诱导的双链伪影的表征和缓解 - PMC (nih.gov) 文献提供的酶切产生的错误识别和去除软件FADE&#xff1a;软件git地址 文献补充材料图&#xff1a;由酶切产生的人工错误序列的碱基质量值偏高&#xff0c;相对其他softclip的质量…

适配Dell R750xs server Broadcom BCM57412 NetXtreme-E 10Gb SFP+ 网卡

摘要 The Issue is to handle Scaler v8.2-385 (baf7f3a) on Dell R750xs server, the 10G NIC card is Broadcom BCM57412 NetXtreme-E 10Gb SFP. Firmware is the latest, but driver is not. The symptom is that only one port or no port could be recognized. After patc…

基于MAX7800羽毛板语音控制ESP8266小车

1. 项目介绍 基于MAX7800羽毛板语音控制ESP8266小车 采用现成的KWS20关键词&#xff0c;[up, down, left, right, stop, go, yes, no, on, off, one, two, three, four, five, six, seven, eight, nine, zero]&#xff0c;进行语音关键字识别远程控制小车。 2. 项目设计思路 搭…

JSP——EL表达式

✅作者简介&#xff1a;热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏&#xff1a;JAVA开发者…

年度征文 | 回顾2022,展望2023 (清风与我)

个人简介&#xff1a; 名称内容个人主页清风与我学习方向主攻前端方向推荐学习vue&#xff0c;vue3&#xff0c;node.js&#xff0c;React&#xff0c;项目实战推荐学习用网站菜鸟教程&#xff0c;博客&#xff0c;哔哩哔哩 …学习用资源网vue&#xff0c;element&#xff0c;…

Apollo星火计划学习笔记——Apollo开放空间规划算法原理与实践

文章目录1. 开放空间规划算法总体介绍1.1 Task: OPEN_SPACE_ROI_DECIDER1.2 Task: OPEN_SPACE_TRAJECTORY_PROVIDER1.3 Task: OPEN_SPACE_TRAJECTORY_PARTITION1.4 Task: OPEN_SPACE_FALLBACK_DECIDER2. 基于混合A *的路径规划算法2.1 hybrid A*的简要思想2.2 RS曲线2.3 Apollo…

一文读懂方壳电池仓段差缺陷检测

摩根大通预计&#xff0c;2025年中国新能源汽车渗透率将达46.3%&#xff0c;彭博新能源财经最新发布的《新能源汽车市场长期展望》(EVO)报告显示&#xff0c;到2025年底&#xff0c;新能源汽车保有量将达到7700万辆&#xff0c;不难看出新能源行业依旧前景广阔。围绕“新能源”…

JDBC(Java Database connect)详解

一、相关概念 什么是JDBC JDBC&#xff08;Java Data Base Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API&#xff0c;可以为多种关系数据库提供统一访问&#xff0c;它由一组用Java语言编写的类和接口组成。JDBC提供了一种基准&#xff0c;据此可…

软件测试门槛低么?适合初学者么

随着软件工程活动的不断演化&#xff0c;软件测试工作已经成为了不可或缺的一部分&#xff0c;测试工作某种程度上是可以很大幅度提高软件的产品质量以及提升用户的使用满意度。因此&#xff0c;许多想要学习软件测试的朋友也许常常会有这样的困惑&#xff0c;软件测试门槛低吗…

JS数组对象——根据日期进行排序Date.parse(),按照时间进行升序或降序排序localeCompare()

JS数组对象——根据日期对象进行排序&#xff0c;按照时间进行升序或降序排序场景复现封装数组对象的排序方法根据日期和时间对象排序1、按照日期时间混合排序2、分别按照日期和时间进行排序场景复现 排序在项目中非常实用&#xff0c;出现频率极高&#xff0c;尤其是后台管理…

用javascript分类刷leetcode17.栈(图文视频讲解)

目录 Stack的特点&#xff1a;先进后出&#xff08;FILO&#xff09; 使用场景&#xff1a;十进制转2进制 函数调用堆栈 js里没有栈&#xff0c;但是可以用数组模拟 42/2 42%20 21/2 21%21 10/2 10%20 5/2 5%21 2/2 2%20 1/2 1%21 stack: [0,1,0,1,0,1] res: 1 0 1 …

VC#复习资料

一、选择题 2、“闪电”图标 3、using命名空间 命名空间的设计目的是提供一种让一组名称与其他名称分隔开的方式&#xff0c;using 关键字表明程序使用的是给定命名空间中的名称&#xff0c;使用 using 命名空间指令&#xff0c;这样在使用的时候就不用在前面加上命名空间名称…

golang学习

由于期末考试没时间学算法学了一波go放松一下 这可能是我学语言最认真的一次了&#xff08; 跟的是尚硅谷学完的 二倍速快进 折腾了一周左右 网络编程部分没看 因为不懂计网 不想学&#xff08; 虽然已经很老的课了 但是顺平老师雀氏讲的太细了也是听完了自己也没时间写笔记 …