C++入门(二)

前言

我们上一期介绍了什么是C++,命名空间、输入输出、以及缺省参数。本期我们来继续介绍C++的入门知识!

本期内容介绍

函数重载

引用

内联函数

auto关键字

范围for

指针空值nullptr

目录

前言

本期内容介绍

一、函数重载

什么是函数重载?

C++支持函数重载的原理

二、引用

什么是引用?

引用的特性

常引用

再谈临时变量

引用的使用场景

1、做参数

2、做返回值

引用和传值的效率比较

传参和传引用比较

值返回和引用返回比较

引用和指针的区别

三、内联函数

什么是内联函数?

内联函数的特性

宏的优缺点

四、auto关键字(C++11)

auto关键字介绍

auto使用细节

五、范围for(C++11)

范围for语法

范围for的使用条件

六、指针空值nullptr(C++11)


一、函数重载

什么是函数重载?

函数重载:同一作用域中函数名相同、形参列表不同的函数。形参列表不同是指:参数的个数、类型、顺序不同! 函数重载对于函数的返回值没有要求!

OK,举个栗子:

void Add(int x, int y)
{
	cout << "haha" << endl;
}

int Add(int a)
{
	return a + 10;
}

double Add(int x, double d)
{
	return x + d;
}

double Add(double d, int x)
{
	return x + d;
}

C++支持函数重载的原理

首先C语言是不支持函数重载的,而C++是支持函数重载的!

C语言不支持的原因可以从两方面解释。

1、历史角度:设计C语言的祖师爷当时觉得设计同名函数会导致误会,而且那时候没有碰到相应的场景,因此没有设计同名函数!

2、链接角度:C语言在生成可执行程序前需要编译链接,链接时去合并符号表和符号表的重定位时,是直接去拿函数名去找的(C语言没有同名函数)。

C++支持函数重载的原因是,C++会对重载的函数在编译时进行特殊的符号修饰,等到链接去合并符号表和符号表的重定位的时候,实际上是根据不同的函数名(进行修饰过的)进行找的!

OK,这里大概串一下编译链接的过程:

OK,再来分别看看C语言和C++的编译后的函数名的区别,由于windows函数修饰规则较为复杂所以我这里以Linux为例(add函数为例):

先通过C语言的编译器gcc进行编译到汇编停止(为了待会看符号表)

再来看看C++的:

通过C++的编译器g++进行编译到汇编停止(C++也是生成.o):

显然C语言生成的符号表的函数名和C++是不一样的,C语言就是纯函数名而C++是修饰过的函数名!这里再来介绍一下C++的函数名的修饰规则(Linux下)!

—Z + 函数名的字符个数 + 函数名 + 参数类型的首字符

所以上面C++编译后的符号表中的add是_Z3addii,因为它的函数名有三个字符,函数参数类型都是int所以是ii

二、引用

什么是引用?

引用(&)不是重新定义一个变量,而是给已经存在变量起一个别名,而是引用变量和被引用的变量共用一块空间!(语法上编译器不会为引用变量开空间

语法:类型& 引用变量(对象)名 = 引用实体

OK,举个例子:我们小时候看西游记的时候,孙悟空又称齐天大圣,齐天大圣就是孙悟空的别名即引用!

#include <iostream>
using namespace std;

int main()
{
	int a = 10;
	int& b = a;
	cout << a << "--" << b << endl;
	b++;
	cout << a << "--" << b << endl;
	return 0;
}

这里b就是a的引用,他是a的别名,和a共用一块空间!既然他和a共用一块空间的话对b++,a也会+1,他们的地址应该也是一样的!

但我们刚刚上面说是语法上引用不开空间,那底层呢?底层会!引用的底层就是指针(汇编实现)!我们跳到反汇编看看:

我们平时学习时认为引用是不开空间的但也要知道底层编译器是开了的,它的大小是4/8个字节!

注意:引用类型必须和引用实体同类型的

不能这么乱搞!!!

引用的特性

1、引用在定义时必须初始化

2、一个变量可以有多个引用(就像孙悟空别名是齐天大圣、弼马温、猴子等)

3、引用一旦引用了一个实体,就不能在引用其他实体了

1、引用在定义时必须初始化

int main()
{
	int a = 3;
	int& b; 
	return 0;
}

2、一个变量可以有多个引用

int main()
{
	int a = 3;
	int& b = a; 
	int& c = a;
	int& d = c;
	return 0;
}

3、引用一旦引用了一个实体,就不能在引用其他实体了

int main()
{
	int x = 10;
	int y = 20;
	int& c = x;
	cout << x << endl;
	cout << c << endl;
	c = y;//此处不是改变了引用实体,而是把y赋值给了c引用的实体
	cout << c << endl;
	cout << y << endl;
	cout << &x << endl;
	cout << &c << endl;
	cout << &y << endl;
	return 0;
}

这里通过打印地址验证了引用一旦引用一个实体就不在改变!

常引用

这里的常引用实际上是权限的问题!

权限可以缩小、可以平移,但权限不能放大!

权限不能放大

int main()
{
	const int a = 10;//const 修饰的常变量具有常量的属性-->不可修改
	int& b = a;//权限放大
	return 0;
}

我们知道被const修饰的变量具有常量的属性--->不可修改,下面用非const 修饰的引用b去引用a,此时的b是可以修改的即放大了权限

解决办法-->缩小引用的权限让引用也被const修饰(权限可以平移):

权限可以缩小

int main()
{
	int x = 10;
	const int& y = x;//权限缩小
	return 0;
}

赋值是不涉及权问题

int main()
{
	const int x = 100;
	int c = x;//赋值
	return 0;
}

说道这里我又想到了一个小知识点 ---- 临时变量,我们顺便来介绍一下:

再谈临时变量

我们以前在C语言函数返回值的时候介绍过,函数返回会创建临时变量,临时的中间变量具有常量属性!当时没有细细的介绍只是一笔带过了!其实这条性质不只是返回值有效,对于类型转换(强转、隐式类型转换、整型提升、截断等)都是有效的!

OK,举个栗子:

int main()
{
	int a = 3;
	double d = a;
	return 0;
}

这里是典型的隐式类型转换,int转换为double ,其实他并不是直接把a(int)给转换为d(double)的,而是产生了中间变量的!这个变量是编译器产生并且默默处理的,无法调试看到!我画个图解释一下:

还有就是我们当时在模拟实现内存函数(memcpy、memmove、movecmp)的时候说过:强转具有临时性不能被修改!原因就是这!

OK,验证一下:

int main()
{
	int a = 10;
	double d = 9.9;
	int* pa = &a;
	double* pd = &d;
	(int*)(&d) = nullptr;
	return 0;
}

引用的使用场景

1、做参数

我们以前在用C语言交换两个整数时是不是得传指针呀,原因是形参是实参的一份临时拷贝,改变形参不改变实参,要改变实参必须传地址!今天有了引用就不用了,引用是别名,就可以直接修改了!

以前版本:

void Swap(int* x, int* y)
{
	int tmp = *x;
	*x = *y;
	*y = tmp;
}

int main()
{
	int a = 3, b = 5;
	printf("交换前: a = %d  b = %d\n", a, b);
	Swap(&a, &b);
	printf("交换后: a = %d  b = %d\n", a, b);
	return 0;
}

引用做参数版本:

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 3, b = 5;
	printf("交换前: a = %d  b = %d\n", a, b);
	Swap(a, b);
	printf("交换后: a = %d  b = %d\n", a, b);
	return 0;
}

介绍到这里,我们可以在实现数据结构的时候如果不带头的话必须得要二级指针的地方,有点不方便,今天介绍了引用后我们可以不用传二级了,直接把以及传过去行参用一个一级指针的引用接收即可!

没使用引用前:

typedef struct SLNode
{
	int data;
	struct SLNode* next;
}SLNode;

//头插
void SLPushBack(SLNode** head)
{

}

SLNode* head = NULL;
SLPushBack(&head);

使用引用之后:

typedef struct SLNode
{
	int data;
	struct SLNode* next;
}SLNode, *SLLink;

//头插
void SLPushBack(SLNode*& head)
{

}

SLLink head = NULL;
SLPushBack(head);

是不是看起来好点了,而且操作也会简单一点!使用引用的这种方式是很多教材上的样例!而且还在书皮上写着" 数据结构与算法 "C语言实现???我也寻思C语言没有引用啊???我当时一开始看到学校的教材时直接傻眼了!(自以为链表学的还行结果上机操作不来,当时还没学C++....)!我个人觉得第二种这种教材的写法应该告诉读者要提前学习C++引用。不然对新手极其不友好!!!!

2、做返回值
int& Count()
{
	static int n = 0;
	n++;
	return n;
}
int g_val = 0;
int& Count(int n)
{
	g_val += n;
	return g_val;
}

这两种写法是没有问题的!全局变量和静态变量都存在于静态区,他们的生命周期都是整个工程的生命周期。所以这里可以直接返回,因为出了作用域引用的实体(对象)还存在!

思考一下,下面这段代码对吗?能这样写吗?

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

int main()
{
	int& ret = Add(1, 2);
	cout << "Add(1, 2) is :" << ret << endl;
	Add(3, 4);
	cout << "Add(1, 2) is :" << ret << endl;
	return 0;
}

先看结果:

你要是在公司写这样的代码出来,你的领导200%会找到你对你进行“鼓励+问候”的!!!你肯定想我第二次内接受呀为什么是7?这段代码问题实际是返回栈空间的地址,和我们C语言指针那一期介绍的那个一样!OK,我来画个图解释一下为什么会这样!

注意:引用是否做返回值,取决于出了作用域后引用的实体是否存在,如果存在可以用引用做返回值,否则用传值!

引用和传值的效率比较

我们以前讨论传值传参和传址传参哪个优时?我们的结论是:在修改参数的值时、参数大小较大时,传址是更好的原因是1、改变必须得要指针2、较大的参数如果传值时会产生拷贝,效率会降低!如果是传指针只是4/8个字节!!其实在适当的场景下引用的效率也是远远地高于传值的!我们来看看!

以结构体为例:

struct A 
{
	int a[100000];
	char b;
	double c;
};
传参和传引用比较

这样一个结构体,它的大小是不是很大呀!如果传值过去的话形参就会产生拷贝导致效率很低!传引用的话,形参是实参别名,语法上不开空间!效率会高很多!

OK,我们来验证一下:

void TestA1(struct A a1){}
void TestA2(struct A& a2) {}

int main()
{
	struct A a1;
	size_t begin1 = clock();
	for(int i = 0; i < 10000; i++)
		TestA1(a1);
	size_t end1 = clock();
	cout << "传值消耗的时间: " << end1 - begin1 << endl;

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
		TestA2(a1);
	size_t end2 = clock();
	cout << "传引用消耗的时间: " << end2 - begin2 << endl;

	return 0;
}

值返回和引用返回比较

一般的值返回是会形成一个拷贝的(大一点的是形成拷贝在放在被调函数的栈帧上,较小的会放在寄存器eax里面。这里我们在函数栈帧介绍过,就不在说了)然后返回,实际上我们看到的返回值在出了作用域后就会销毁!如果是出了作用域对象还在的话,用引用返回的确比较好的多!但一定注意出了作用域实体得存在!

struct A 
{
	int a[100000];
	char b;
	double c;
};
struct A a1;

A TestA1() {
	return a1;
}
A& TestA2() {
	return a1;
}

int main()
{
	size_t begin1 = clock();
	for(int i = 0; i < 10000; i++)
		TestA1();
	size_t end1 = clock();
	cout << "值返回消耗的时间: " << end1 - begin1 << endl;

	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
		TestA2();
	size_t end2 = clock();
	cout << "引用返回消耗的时间: " << end2 - begin2 << endl;

	return 0;
}

总结:上述测试说明了值和引用在传参、返回值上的效率有很大的不同,如果较大的参数建议用引用传!一定注意的是:是否用引用返回时,取决于引用的实体等出了作用域还在!否则出问题!

引用和指针的区别

在语法上引用就是一个别名,没有独立空间,他和引用的实体共用一块空间!但在底层上是开了的,底层是用汇编语言通过指针实现的!

语法上没开空间:

int main()
{
	int a = 10;
	int& b = a;
	cout << &a << endl;
	cout << &b << endl;
	return 0;
}

底层开了空间:


int main()
{
	int a = 10;
	int& b = a;
	int* p = &a;
	return 0;
}

主要区别:

1、引用语法上是定义一个变量的别名,指针存储着一个变量的地址

2、引用在定义时必须初始化,指针没有要求

3、引用在引用了一个实体后就不能在引用别的实体了,指针可以在任何时候指向同类型的实体

4、引用没有空引用,指针有空指针

5、sizeof下的含义不同:引用的结果为引用实体类型的大小,指针的结果为4/8(x86和x64)

6、引用自加引用的实体会加一,指针自加指针会偏移一个指向类型的字节大小

7、引用没有多级,指针有多级

8、访问实体的方式不同:引用不需要显示处理编译器会处理,指针需要显示解引用

9、引用相较于指针更安全

三、内联函数

什么是内联函数?

被inline修饰的函数叫做内联函数!在编译时C++编译器会在调用内联函数的地方直接展开而不在建立函数栈帧了!(这提升了程序的运行效率)

OK,举个栗子:

不是内联函数(有函数栈帧):

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

内联函数(不会创建函数栈帧):

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

由于小编的是VS2019在这块优化的有点大,调试不来,我们在VS2013上看看:

内联函数的特性

1、C语言不支持内联函数

2、内联(inline)是一种典型的空间换时间的做法,如果编译器把函数当做内联来处理,在编译阶段会直接在原地展开而不去调用(call)。

这样做的优点是:少了栈帧开销,程序的运行效率提高了,缺点是:直接展开如果调用的地方过多会使目标文件(可执行程序)变大!

OK,举个栗子:

3、inline对于编译器而言只是一个建议,不同的编译器对于inline的实现不同!但一般是较小规模的函数(一般不超过10行,具体各个编译器不同)、非递归、频繁调用的函数编译器会采用inline修饰即在调用地展开,否则编译器会直接忽略掉inline的特性

下面是《C++prime》第五版关于inline 的建议:

OK,举个栗子(上面那个已经验证过内联了,我们来个编译器忽略的):

inline int fun(int n)
{
	int x = 10;
	int q = 100;
	int w = 1;
	int a = 3;
	int z = 2;
	int c = 21;
	int v = 5;
	int f = 6;
	int g = 7;
	int u = 9;
	return x + 10;
}

我个人觉得这应该是防止程序员乱搞的一种机制!不然编译器不制止的话,有的给你到处无脑inline,程序就会很大很大!有了这个机制后就相当于是程序员向编译器发出请求,编译器觉得不合理了就可以拒绝!

4、inline是不建议申明和定义分开的,如果分离会在链接时找不到inline函数!因为inline是在调用地直接展开的没有地址,在编译完之后进行链接符号表合并的时候就找不到inline!

C++中的内联的作用实际上是为了解决C语言的宏的缺陷的!我们顺便来回忆一下宏的优缺点!

宏的优缺点

优点:

1、增强了代码的复用性

2、提高性能

缺点:

1、不方便调试(在预处理阶段就已经替换了)

2、导致代码的可读性差、可维护性差、容易误用

3、没有类型检查

别的不说,基本功不扎实的写一个简单的加法的宏都会写错!下面是典型的例子(看看有没有你的影子)。

错误版本:

//#define ADD(int x, int y) return x + y;
//#define ADD(x, y) return x + y;
//#define ADD(x, y) x + y;
//#define ADD(x, y) (x + y)
//#define ADD(x, y) (x) + (y)

下面这三个还是能理解的,上面那两个是真的无语的那种!!!有的伙伴看不出下面三种哪里不对,我来分别用一个例子演示:

#define ADD(x, y) x + y
int main()
{
	int ret = ADD(2 , 3) * 5; // 替换后--> 2 + 3 * 5
	return 0;
}
#define ADD(x, y) (x + y)
int main()
{
	int ret = ADD(2 | 1, 1 & 3); // 替换后--> 2 | 1 + 1 & 3
	return 0;
}

这里+的优先级比&|高得多,与我们的期望值计算结果不一样!

#define ADD(x, y) (x) + (y)
int main()
{
	int ret = ADD(2,3) * 6; // 替换后--> (2) + (3) * 6;
	return 0;
}

下面这个也是替换后的优先级有问题!下面我们来看看正确的写法

#define ADD(x, y) ((x) + (y))

C++的内联函数实际上是很看不起宏的,它的语法细节太多了,稍不注意就会出错!所以C++是通过内联函数替代宏、用const 和 enum来替代常量。这就完美了解决了C语言宏的缺陷!内联可以调试,有类型检查,可读性强!

四、auto关键字(C++11)

随着学习的深入,程序变得越来越复杂,程序设计的类型也越来越复杂!这就会导致两个问题的产生:1、类型难于拼写 2、含义不明确导致出错

OK,举个栗子:

#include <string>
#include <map>
int main()
{
	std::map<std::string, std::string> m{ { "apple", "苹果" }, { "orange",
   "橙子" },{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();

	return 0;
}

std::map<std::string, std::string> 这么长的类型就问你恶不恶心?

这里你肯定说可以用typedef 重命名,的确可以!

#include <string>
#include <map>

typedef std::map<std::string, std::string> Map;
int main()
{
	Map m{ { "apple", "苹果" },{ "orange", "橙子" }, {"pear","梨"} };
	return 0;
}

但有时候typedef会把你坑死~!再来看个栗子:

typedef char* pstring;
int main()
{
	const pstring p1; 
	const pstring* p2; 
	return 0;
}

这样写对吗?是不是连你都控制不住了!OK,这里是p1有问题p2可以的!为什么p1不行p2可以呢?我来解释一下:先看运行结果:

这个就对基本功考察的很深了!而且很难控制和推导出类型!为此C++11引进了一个关键字:auto

关于auto有伙伴可能听过一些!没错他就是C语言中最没有存在感的关键字!在每个局部变量前都有一个隐藏的auto,我们一般不写,也可以显示的写出来:

#include <stdio.h>
int main()
{
	auto int a = 10;
	printf("%d\n", a);

	struct A
	{
		int b;
	};

	auto struct A b;

	return 0;
}

auto关键字介绍

在早期 C/C++ auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它! C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一 个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得到

举个栗子:

int main()
{
	int a = 6;
	cout << a << endl;
	auto x = 10;
	cout << x << endl;
	auto d = 6.6;
	cout << d << endl;
	auto c = 'a';
	cout << c << endl;
	return 0;
}

这样写可以吗?

int main()
{
	auto a;
	cout << a << endl;
	return 0;
}

肯定不行的!为什么呢?人家说的很明确:

auto声明的变量必须由编译器在编译时期推导而得到。换句话说用auto声明变量时,它的底层编译器会根据右边的类型确定auto的实际类型的!这里啥都不给让编译器推到啥???

那我们平时怎么知道auto申明的变量或返回的类型是什么类型呢?这里介绍一个typeid关键字来查看!如下:

typeid(查看对象名).name()

char Test()
{
	return 'a';
}

int main()
{
	auto d = 6.6;
	cout << d << endl;
	cout << typeid(d).name() << endl;

	auto ret = Test();
	cout << ret << endl;
	cout << typeid(ret).name() << endl;

	return 0;
}

总结:

在使用auto定义变量时必须初始化 ,因为在编译期间编译器要根据初始化的内容去推导auto的实际类型!所以auto并不是一种类型的声明,他是一种类型的 占位符 ,在编译期间编译器会将他替换成实际的类型!

auto使用细节

1、auto声明指针变量时,auto和auto*没有区别,但auto声明引用类型时则必须加&

int main()
{
	int a = 10;
	auto p1 = &a;
	auto p2 = &a;
	cout << typeid(p1).name() << endl;
	cout << typeid(p2).name() << endl;
	auto& b = a;

	return 0;
}

2、在同一行定义多个变量时,多个变量的类型必须是一样的,否则会报错!原因是:编译器会只推导第一个类型,然后用这个类型去定义后面的其他变量!

int main()
{
	auto a = 10, b = 20;//这里不会报错
	auto d = 6.6, c = 'a';//这里会报做
	return 0;
}

3、auto不能做函数的参数

void Test(auto x)
{
	//...
}

4、auto不能声明数组

int main()
{
	auto a[] = { 1,2,3 };
	return 0;
}

auto其实还有其他用法例如配合范围for和lambda表达式使用!后期介绍到了再详解~!

五、范围for(C++11)

C++98 中如果要遍历一个数组,可以按照以下方式进行:
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
     array[i] *= 2;
for (int* p = array; p < array + sizeof(array)/ sizeof(array[0]); ++p)
     cout << *p << endl;
}
对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因
C++11 中引入了范围的 for 循环。

范围for语法

for 循环后的括号由冒号 分为两部分:第一部分是范 围内用于迭代的变量,第二部分则表示被迭代的范围
for( 集合每个元素的类型 变量名 :集合) {}
他这里实际上是从集合中拿出一个元素赋值给前面声明的变量,然后自动++,他会自动判断结束的!!
void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
     e *= 2;
for(auto e : array)
     cout << e << " ";
return 0;
}
注意:与普通循环类似,可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环

范围for的使用条件

1、for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供
begin end 的方法, begin end 就是 for 循环迭代的范围。
来看看这段代码对吗?
void TestFor(int array[])
{
    for(auto& e : array)
        cout<< e <<endl;
}

答案是错的!我们上面说过!范围for迭代的范围必须是确定的!这里array是一个数组,但我们知道虽然他这么写看起来是数组,但他的本质是一个指针,arrasy的范围是不确定的!所以无法遍历!

2. 迭代的对象要实现++和==的操作。 (关于迭代器,以后会介绍,现在提一下,知道有这个东西就好了)

六、指针空值nullptr(C++11)

在良好的C/C++编程习惯中应在声明一个变量时给这个变量一个合适的初始值,否则可能出现不可预料的错误!例如指针不初始化。C语言中如果一个指针没有合适的指向的话我们一般会给他置NULL(NULL实际上本质就是0),但在C++中会出现一些问题!
NULL实际是一个宏,在传统的C头文件(stddef.h)中:
int main()
{
	int* p1 = NULL;

	return 0;
}

上面定义中我们看到了NULL被定义成了,字面常量0或被定义为无类型的指针的常量void*。这样定义会在成一些问题,看如下代码:

void Test(int)
{
	cout << "Test(int)" << endl;
}

void Test(int*)
{
	cout << "Test(int*)" << endl;
}

int main()
{
	Test(0);
	Test(NULL);
	return 0;
}
我们的本意是让第二个NULL走int*,但他的结果好像和我们的预期不一样呢!
此时要想让NULL调到Test(int*)必须进行强转!
这样写是不是有点麻烦呀!这其实是C++在这里的一个缺陷!后来C++11为了解决这个缺陷对这里打了补丁 ---> 用nullptr替换NULL
注意:
1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr C++11 作为新关键字引入
2. C++11 中, sizeof(nullptr) sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr
OK,C++入门知识就介绍到这里,好兄弟,我们下期再见!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apple :苹果将在明年年底推出自己的 AI,预计将随 iOS 18 一起推出

本心、输入输出、结果 文章目录 Apple &#xff1a;苹果将在明年年底推出自己的 AI&#xff0c;预计将随 iOS 18 一起推出前言三星声称库克相关图片弘扬爱国精神 Apple &#xff1a;苹果将在明年年底推出自己的 AI&#xff0c;预计将随 iOS 18 一起推出 编辑&#xff1a;简简单…

Java关于由子类构造器生成的父类对象的反射问题

Java关于由子类构造器生成的父类对象的反射问题 问题概括一、案例准备二、问题描述 问题概括 提示&#xff1a;这里我就不绕圈子直接描述&#xff1a; Java中由子类构造器生成的父类的getclass.getName不是父类的类名而是子类的类名&#xff0c;因此不可以用子类构造器生成的…

2023年【安全员-B证】新版试题及安全员-B证免费试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-B证新版试题参考答案及安全员-B证考试试题解析是安全生产模拟考试一点通题库老师及安全员-B证操作证已考过的学员汇总&#xff0c;相对有效帮助安全员-B证免费试题学员顺利通过考试。 1、【多选题】下列哪些属…

HTML表格学习

HTML学习笔记二 HTML表格&#xff1a; HTML 表格由 标签来定义。 HTML 表格是一种用于展示结构化数据的标记语言元素。 tr&#xff1a;表示表格的一行。td&#xff1a;表示表格的数据单元格。th&#xff1a;表示表格的表头单元格。 数据单元格可以包含文本、图片、列表、段…

Scala爬虫实战:采集网易云音乐热门歌单数据

导言 网易云音乐是一个备受欢迎的音乐平台&#xff0c;汇集了丰富的音乐资源和热门歌单。这些歌单涵盖了各种音乐风格和主题&#xff0c;为音乐爱好者提供了一个探索和分享音乐的平台。然而&#xff0c;有时我们可能需要从网易云音乐上获取歌单数据&#xff0c;以进行音乐推荐…

8086读取键盘-磁盘输入

文章目录 前言1.从键盘读数据2.磁盘读数据 前言 想过一个问题没有&#xff0c; 8086是如何从键盘中接受输入的&#xff1f; 8086如何将字符在显示器上显示的&#xff1f; 8086如何从磁盘中读取数据的&#xff1f; 上面的问题都是没有操作系统的时候&#xff0c;比如bios的那段…

Hive 知识点八股文记录 ——(一)特性

Hive通俗的特性 结构化数据文件变为数据库表sql查询功能sql语句转化为MR运行建立在hadoop的数据仓库基础架构使用hadoop的HDFS存储文件实时性较差&#xff08;应用于海量数据&#xff09;存储、计算能力容易拓展&#xff08;源于Hadoop&#xff09; 支持这些特性的架构 CLI&…

python解析xmind统计测试用例/测试点 个数及执行情况

前言&#xff1a;统计的是每个分支最后一个节点的状态 xmind版本 23.0911172 标记打开位置 标记规则如下 解释&#xff1a; res {"total": 0, "pass": 0, "fail": 0, "no_result": 0, "unfinished": 0, "now_fail…

SUSE 12双网卡绑定

原创作者&#xff1a;运维工程师 谢晋 SUSE 12双网卡绑定 客户环境及需求网卡绑定 客户环境及需求 客户一台物理机安装了SUSE 12的操作系统&#xff0c;需要将ETH5和ETH7双网卡聚合为一块虚拟网卡&#xff0c;以保证一块网卡故障不会影响系统正常运行。 网卡绑定 输入命令c…

C++冒号的作用域

当同时定义了一个全局变量a和局部变量a&#xff1a; 结果输出了局部变量的10&#xff0c;因为程序遵循就近原则。 :: 代表全局作用域 如果想无视就近原则&#xff0c;打印全局变量的a&#xff0c;就在输出时把a的前面加两个冒号。 ::

机器视觉目标检测 - opencv 深度学习 计算机竞赛

文章目录 0 前言2 目标检测概念3 目标分类、定位、检测示例4 传统目标检测5 两类目标检测算法5.1 相关研究5.1.1 选择性搜索5.1.2 OverFeat 5.2 基于区域提名的方法5.2.1 R-CNN5.2.2 SPP-net5.2.3 Fast R-CNN 5.3 端到端的方法YOLOSSD 6 人体检测结果7 最后 0 前言 &#x1f5…

推荐能用ios端磁力下载工具

关于ios端磁力下载工具&#xff0c;之前的文章给大家介绍过2个&#xff0c;分别是雷电下载和闪电下载。但是如今因为不可抗力和苹果商店对于磁力下载和云盘功能的限制&#xff0c;目前这两款工具已经不能够使用了。也就是说免费的下载工具已经没有了&#xff0c;毕竟实现ios端这…

Ubuntu 20.04源码安装git 2.35.1

《如何在 Ubuntu 20.04 上从源代码安装 Git [快速入门]》和《如何在 Ubuntu 20.04 上安装 Git》是我参考的博客。 https://git-scm.com/是git官网。 lsb_release -r看到操作系统版本是20.04。 uname -r看到内核版本是5.4.0-156-generic。 sudo apt update更新一下源。 完…

STM32G0+EMW3080+阿里云飞燕平台实现单片机WiFi智能联网功能(三)STM32G0控制EMW3080实现IoT功能

项目描述&#xff1a;该系列记录了STM32G0EMW3080实现单片机智能联网功能项目的从零开始一步步的实现过程&#xff1b;硬件环境&#xff1a;单片机为STM32G030C8T6&#xff1b;物联网模块为EMW3080V2-P&#xff1b;网联网模块的开发板为MXKit开发套件&#xff0c;具体型号为XCH…

【RPC】前传

前传 本地程序用的go语言&#xff0c;想把main.go程序当中一些计算工作放到服务器上进行&#xff0c;而只需要把结果给我即可。由于平台上暂时不能运行Go代码&#xff0c;所以写的是python文件。 1、主要是使用ssh依赖进行连接&#xff0c;但是大概率是需要手动添加的&#xf…

JVM Native内存泄露的排查分析(64M 问题)

我们有一个线上的项目&#xff0c;刚启动完就占用了使用 top 命令查看 RES 占用了超过 1.5G&#xff0c;这明显不合理&#xff0c;于是进行了一些分析找到了根本的原因&#xff0c;下面是完整的分析过程&#xff0c;希望对你有所帮助。 会涉及到下面这些内容 Linux 经典的 64M…

AI:68-基于深度学习的身份证号码识别

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌在这个漫长的过程,中途遇到了不少问题,但是…

创建一个自定义关卡资源(一)

需求&#xff1a;需要一个资源属性&#xff0c;是我可以自己用的。例如我需要加载一个关卡A&#xff0c;那么我需要一个资源文件来记录的我关卡A的所有信息&#xff0c;然后我在读取的时候直接读这个资源就能可以了。 首先&#xff0c;设置一个资源文件&#xff0c;如下 using…

linux基础指令【上篇】

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 引用 01. ls 指令2. pwd命…

Selenium之路: UI自动化测试的必备指南

文章目录 一. 什么是自动化测试二. selenium的介绍1. Selenium是什么2. Selenium的工作原理3. Selenium 的环境搭建 三. webdriver API1. 元素的定位1.1 CSS 定位1.2 XPath 定位1.3 实现一个自动化需求 2. 操作测试对象2.1 clear 清除对象输入的文本内容2.2 submit 提交2.3 get…