IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C++入门 -> 正文阅读

[C++知识库]C++入门

本章是对C语言不足地方的改进。

C++的输入和输出

1.使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间;

2.cout和cin是全局的流对象,endl是特殊的C++符号,表示换行,他们都包含在头文件中;

3.<<是流插入运算符,>>是流提取运算符;

4.使用C++输入输出更方便,不需增加数据格式控制(printf、scanf需手动控制格式),比如:整形–%d,字符–%c。C++的cout能够自动识别类型;

5.cout和cin分别是ostream和istream类型的对象,>>和<<也涉及运算符重载,后续再学习。

#include <iostream>

//using namespace std;//全部展开

using std::cout;
using std::endl;

int main()
{
	cout << "hello world" << endl;
	cout << "hello world\n";
	printf("hello world\n");

	int n = 10;
	double d = 1.5;
	
	//cout自动识别类型
	cout << n << " " << d << endl;
	
	printf("%.2lf\n",d);
	printf("%lf\n",d);//默认保留小数点后6位
	
	//从键盘获取
	std::cin >> n;
	cout << n << endl;
	
	const char* str = "hello world";
	cout<<str<<endl;
	return 0;
}

对于printf和cout,哪一个使用方便就用哪一个。

注意】:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可(<iostream.h>),但是后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h();旧编译器(vc 6.0)中还支持<iostream.h>格式(没有命名空间),后续编译器已不支持,因此推荐使用
<iostream>+std的方式。

缺省参数(默认参数)

缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参,则采用该默认值,否则使用指定的实参。

//缺省参数
void test(int a = 10)
{
	cout << a << endl;
}

int main()
{
	test();//10  没有传参时,使用参数的默认值
	test(20);//20  传参时,使用指定的实参
	return 0;
}

全缺省

int add(int a = 10,int b = 20)
{
	return a + b;
}

int main()
{
	int ret = ();//30
	int ret2 = add(100,200);//300
	int ret3 = add(100);//120
	return 0;
}

半缺省
半缺省:部分参数缺省,并且必须从右向左缺省,必须连续缺省

//半缺省
void Fun(int a,int b = 20,int c = 30)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

//void Fun(int a,int b = 20,int c)//错误

int main()
{
	Fun(10);
	Fun(10,1);
	Fun(1,2,3);
	return 0;
}

注意】:

  1. 半缺省参数必须从右往左依次来给出,并且不能间隔着给默认值
  2. 缺省参数不能在函数声明和定义中同时出现(语法就是这样规定的),要么写在声明,要么写在定义,建议写在声明中
  3. 缺省值必须是常量或者全局变量
  4. C语言不支持(编译器不支持)

函数重载

函数重载概念

一个词可以有多重含义,人们可以通过上下文来判断该词真实的含义,即该词被重载了。

函数重载: 是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数类型不同、参数个数 不同或 顺序不同)必须不同,常用来处理实现功能类似数据类型不同的问题。

函数重载的要求满足下面一条即可:
1.参数的类型不同
2.参数的个数不同
3.参数的顺序不同

//函数重载
void f(char a)
{
	cout << "f(char a)" << endl;
}

void f(int a)
{
	cout << "f(int a)" << endl;
}

void f(int a,int b)
{
	cout << "f(int a,int b)" << endl;
}

//顺序不同
void f(char b, int a)
{
	cout << "f(char b,int a)" << endl;
}

1.缺省值不同,不能构成重载

void fun(int a = 10)
{
	cout << a << endl;
}

//不能构成重载
//void fun(int a)
//{
//	cout << a << endl;
//}

针对的是参数的类型,个数、顺序,这里类型相同,个数相同,顺序相同,所以不能构成重载。

2.能构成重载,但是存在问题

//构成重载,但是存在问题
//修饰后的函数名_Z1F
void F()
{
	cout << "F" << endl;
}

//修饰后的函数名_Z1Fi
void F(int a = 0) 
{
	cout << "F(int a)" << endl;
}

修饰后的函数名不同,这两个函数可以构成重载,但是在调用时,存在问题,F()到底是调用哪一个?

以下两个函数不能构成重载,参数类型,参数个数都相同,仅仅是参数名不同,并不能构成重载。

short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}

如果只是返回值不同,并不能构成函数重载(报错),因为调用的时候不能区分是哪个函数。

名字修饰

windows下,VS是根据文件后缀去调用对应的编译器,.c就是c编译器;.cpp就是c++编译器。

在Linux下不用文件后缀来区分编译器,gcc就是c编译器,g++就是c++编译器。

为什么C不支持函数重载,C++支持函数重载?

在此之前,我们学习过在C/C++中,一个程序要运行起来,需要经历以下几个阶段:预处理、编译、汇编、链接。

在这里插入图片描述
在这里插入图片描述

1.预处理阶段:头文件在源文件中展开(展开后就没有头文件了)、宏替换、条件编译、删除注释(生成.i文件)

2.编译:检查语法、生成汇编代码(指令级语言代码)(生成.s文件)

3.汇编:汇编代码转换成二进制机器码、形成符号表(生成.o文件)每个源文件都会生成自己的符号表

4.链接:合并段表,生成可执行程序(a.out)

下面,在Linux下,分析为什么C语言不支持函数重载,而C++支持函数重载。

C语言不支持函数重载的原因:在编译时,两个重载函数,函数名相同,符号表中有两个相同的函数名,那么符号表中存在歧义和冲突,其次链接的时候也存在歧义和冲突,因为他们都是直接使用函数名去表示和查找,而重载函数的函数名相同,必然会有冲突。【在linux下,采用gcc编译完成后,函数名字的修饰没有发生改变。】

在这里插入图片描述

C++支持函数重载的原因:C++的目标文件符号表中不是直接用函数名来标识和查找函数,而是对函数名进行修饰。【在linux下,采用g++编译完成后,函数名字的修饰发生改变,编译器将函数参数类型信息添加到修改后的名字中。】

对于函数名的修饰规则,在不同的编译器下不同,在linux下,g++编译器中,函数修饰后变成【_Z+函数长度+函数名+形参类型的首字母】。

在这里插入图片描述
有了函数修饰规则,只要参数不同,符号表里面重载的函数就不存在二义性和冲突了。

链接的时候,main函数里面去调用两个重载的函数,查找地址时,也是明确的。

注意:如果在当前文件有函数的定义,那么编译时就填上地址;如果当前文件只有函数声明,那么定义就在其他xxx.cpp文件中,编译的时候没有地址,只能在链接时去其他源文件的符号表中根据函数修饰名字去查找地址(声明的地址不是实际的地址,定义的地址才是真的地址),这就是链接的主要作用。

windows下函数名的修饰规则更加复杂,但是原理是相同的。可以阅读文章C/C++ 函数调用约定.

到这里我们就理解了C语言没办法支持重载,是因为同名函数没办法区分,在编译时就会报错。而C++是通过函数修饰规则来区分,只要参数不同,修饰出来的名字就不一样,就支持了重载。

引用

引用的概念

因为C语言中指针的使用比较复杂,所以C++中引入了“引用”的概念。引用不是重新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

引用的使用方法:类型& 引用变量名(引用实体的别名) = 引用实体;

看下面代码:

在这里插入图片描述
变量a和b的地址相同,改变a或者b的值,另一个变量的值也随之变化。
在这里插入图片描述

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

引用在语法层:没有开新空间,就是给原来的变量(空间)取了一个新的名字。

引用的特性

1.引用在定义时必须进行初始化

	int a = 10;
	//int& b;//错误
	
	//引用在定义时必须进行初始化
	int& b = a;

引用在定义时必须进行初始化,因为你必须指明这个引用是哪个变量的别名。

2.一个变量可以有多个引用(一个变量可以有多个别名)

	int a = 10;
	int& b = a;
	//一个变量可以有多个引用(可以有多个名字)
	int& c = a;
	int& d = a;
	int& e = b;

3.引用一旦引用一个实体,就不能再引用其他实体

	int a = 10;
	int& b = a;

	//引用一旦引用一个实体,就不能再引用其他实体
	int i = 100;
	b = i;//这里只是把变量i的值给b,并不是让b成为i的别名

指针是可以变的,可以指向这个变量,一会又可以指向另一个变量;但是引用不可以,引用从始至终只能是同一个变量的别名。

引用的使用场景

1.引用做参数

传参的三种方式:
1.值传递
2.址传递
3.引用传递

//址传递
//g++中修饰后的函数名为_Z4swappipi
void swap(int* a,int* b)
{
	//使用指针
	int tmp = *a;
	*a = *b; 
	*b = tmp;
}

//引用传递
//g++中修饰后的函数名为_Z4swapriri   r:reference
void swap(int& a,int& b)
{
	//1.引用作为形参,a是实参x的别名,b是实参y的别名
	//声明不会开空间,定义才会开空间
	//定义引用时必须进行初始化,但是这里形参是声明,声明引用是不需要初始化的
	int tmp = a;
	a = b;
	b = tmp;
}

//值传递
//g++中修饰后的函数名为_Z4swapii
void swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}

//g++编译器中,三个函数修饰后的函数名不同,三个函数构成重载,
//但是swap(x,y);在调用时存在歧义,
//类似之前学习的void f();和void f(int a = 10);可以构成重载,但是调用时会存在歧义
//编译器不知道调用时传值还是传引用
int main()
{
	int x = 10;
	int y= 20;
	swap(&x,&y);
	//swap(x,y);//有歧义,到底是值传递还是引用传递,会报错
	return 0;
}

上面3个swap函数构成函数重载,引用和值传递看成不同的参数类型,但是swap(x,y)在调用时存在歧义,不能区分到底是值传递还是引用传递。

引用的底层是指针实现,但是编译器编译时,并不会把引用替换成指针,转换成指令时才会替换成指针。在符号表中使用的是修饰后的函数名,使用的还是形参的类型,只要符号表中的函数名不同就能构成重载,语法层面的参数类型不同即可,并不会关注底层是怎么实现的。

指针的引用

typedef struct SLTNode
{
	struct SLTNode* next;
	int data;
}SLTNode,*SLT;

//SLTNode的类型是struct SLTNode
//SLT的类型是struct SLTNode*

//指针的引用作为形参
//void SListInit(SLTNode*& phead)
void SListInit(SLT& phead)
{
	phead = NULL;
}


int main()
{
	int a = 10;
	//b是a的引用
	int& b = a;

	int* p1 = &a;
	//p2是p1的引用
	int*& p2 = p1;
	
	SLTNode s;
	SLT ps= &s;
	SLT& p = ps;
	SListInit(p);
	return 0;
}

在次之前,C语言部分学过输出型参数,在函数外部定义变量,然后传入该变量的地址,在函数内部改变该变量的值并返回,这样的参数是输出型参数。使用引用作为参数也可以实现该功能。

void change(int* pi)
{
	*pi = 100;
}


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

使用引用作为参数:

void change(int& a)
{
	a = 100;
}


int main()
{
	int a = 10;
	//change(&a);
	change(a);
	std::cout << a << std::endl;
	return 0;
}

2.引用作为返回值

看如下代码,使用传值返回

int Add(int a,int b)
{
	int c = a + b;
	//传值返回,实际返回的是c的一份临时拷贝
	return c;
}

int main()
{
	int ret = Add(10,20);
	std::cout << ret << std::endl;

	return 0;
}

这里返回的并不是变量c,因为函数执行完毕,栈帧就销毁了,局部变量c我们就没有使用权限了(函数栈帧销毁之后,才会返回),即使能取到c的值(可能是正确的值,也可能是不正确的值,取决于栈帧销毁时请不清理空间),也是非法访问的。所以,为了解决这个问题,这里会生成一个临时变量tmp,把c的值拷贝给tmp,返回tmp,所以这里返回的是局部变量c的一份临时拷贝。

临时变量tmp存在哪里?
1.如果临时变量比较小(4或者8字节),一般是存储在寄存器中
2.如果临时变量比较大,临时变量存储在上一层函数的栈帧中(调用Add函数的函数栈帧中)。

值传递中形参是实参的一份拷贝,同样的,所有的传值返回都会生成一个拷贝。

使用引用返回

//引用传递返回
//但是会造成非法访问,函数调用结束后,栈帧销毁,c权限就返回给操作系统了
//我们再访问就是非法访问内存
int& Add(int a,int b)
{
	int c = a + b;
	return c;//返回引用的意思就是不会生成c的拷贝返回,而是直接返回c的引用
}

int main()
{
	int& ret = Add(10,20);

	//cout也会调用栈帧
	std::cout << ret << std::endl;//30

	//栈帧销毁后,调用别的函数就会重复利用销毁的空间,内存是可以重复使用的
	//但是只要不覆盖到上一次调用函数时存入栈帧的数据,就不会被改变

	Add(1, 2);

	std::cout << ret << std::endl;
	//值可能为30,要看内存有没有清理、有没有被覆盖

	return 0;
}

传值返回,会拷贝给临时对象,临时对象返回;引用返回的意思就是不会生成c的拷贝再返回,而是直接返回c的引用(可以理解为返回c的引用int& tmp)。

但是这段代码存在问题

1.存在非法访问内存,因为Add(10,20)的返回值是c的引用,所以Add栈帧销毁后,c已经销毁,再访问c就是非法访问内存;

2.如果Add函数栈帧销毁,并清理空间,那么取c值的时候取到的就是随机值,返回的也就是随机值,当然这个取决于编译器的实现(VS2019,对于malloc出来的空间,free释放后,一般会清理内存,函数调用结束后,栈帧销毁,不一定清理内存)。对于“非法写”编译器一般能检测出来,“非法读”编译器有时候检测不出来。

但是一般不建议使用引用返回,如果要使用,正确的使用方式如下:

如果函数返回时,出了函数作用域,如果返回对象还未还给系统(比如静态变量、全局变量等),则可以使用引用返回;如果已经还给系统了,则必须使用传值返回。

int& Count()
{
	//static修饰变量,存在静态区
	static int n = 0;
	n++;
	// ...
	return n;
}

引用的价值:

1.做参数 – 提高效率;形参的修改可以改变实参(输出型参数)

typedef struct Stu
{
	int age;
	char name[30];
}Stu;

//指针做参数
void Func1(Stu* ps)
{

}

//值传递
void Func2(Stu s)
{

}

//引用做参数
void Func3(Stu& rs)
{

}

2.做返回值 – 提高效率;修改返回变量

//引用作为返回值
int& fun(int i) 
{
	static int a[10];
	return a[i];//没有产生空间,只是产生了一个临时变量int& tmp
}
int main()
{
	for (int i = 0;i < 10; i++)
	{
		fun(i) = 10 + i;
	}

	for (int i = 0; i < 10; i++)
	{
		std::cout << fun(i) << std::endl;
	}
	return 0;
}

传值返回,会拷贝给临时对象,临时对象被返回;
传引用返回,返回的是返回对象的引用(别名)。

不要返回局部变量的引用。

值传递、引用传递效率比较

传值返回和传引用返回的区别:传值返回会有拷贝;传引用返回没有拷贝。

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

以下代码测试值传递和引用传递的效率:

#include <iostream>
#include <time.h>
using namespace std;

struct A 
{ 
	int a[10000]; 
};

//值传参
void TestFunc1(A a) 
{

}

//引用传参
void TestFunc2(A& a) 
{

}

void TestRefAndValue()
{
	A a = { {0} };

	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();

	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();

	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}

int main()
{
	TestRefAndValue();
	return 0;
}

运行这段代码,结果如下:
在这里插入图片描述
数据量非常大时,引用传递效率有显著提升。

总结:引用的作用主要体现在传参和传返回值

1.在有些场景下,可以提高性能。比如大对象+深拷贝对象

2.输出型参数和输出型返回值,也就是说,在有些场景下,形参的改变可以改变实参;有些场景下,引用返回,可以改变返回对象,后续学习再进行补充。

常引用

int main()
{
	const int a = 10;//a的值不能改变
	//int& b = a;//权限放大,因为a是只读的,但是b是a的引用,b是可读可写的,把权限放大了,错误

	const int& b = a;//正确,权限不变

	int c = 20;//c可读可写
	const int& d = c;//权限缩小,d只读,d是c的别名,d只读,是可以的
	return 0;
}

注意:使用引用时:权限不能放大,权限可以不变或者缩小。

void f(int& x)
{
	std::cout << x << std::endl;
}

int main()
{
	const int a = 10;
	//f(a);//错误,因为a是只读的,但是f(int& x)中x是可读可写的,把权限放大了
	
	//int& b = a;//错误,b把权限放大了
	//f(b);
	return 0;
}
void f(const int& x)
{
	std::cout << x << std::endl;
}

int main()
{
	const int a = 10;
	const int& b = a;//b是a的引用
	f(b);//这里a,b,x共用一块空间
	return 0;
}

在这里插入图片描述
这里,调用f(b)时,变量a,b,x共用同一块内存空间,b和x是a的引用。

假设x是一个大对象或者是以后要学习的深拷贝的对象,那么尽量用引用传参,减少拷贝。如果f函数中不改变x,建议尽量用常引用传参。

//使用常引用,在函数内部不能改变x的值
void f(const int& x)
{
	std::cout << x << std::endl;
}

int main()
{
	int a = 10;
	f(a);//权限缩小

	const int b = 20;
	f(b);//权限不变

	return 0;
}

临时变量具有常属性

int main()
{
	int a = 30;
	const int& b = a;

	double d = 3.14;
	int c = d;//隐式类型转换

	//int& e = d;//错误

	const int& f = d;
	//这里有隐式类型转换,会产生一个临时变量来存储结果,f引用的是临时变量,而临时变量是具有常属性的,所以引用变量f的类型和临时变量保持一致,所以应该是const int&,引用权限只能不变或者缩小。

	return 0;
}

隐式类型转换:产生一个临时变量(中间变量),把临时变量赋值给左值;整型提升、算术转换操作的都是临时变量,并不会改变原来的值。

临时变量具有常性,不能被修改(临时变量是右值)。
常引用const Type&可以接收各种类型的对象。

引用和指针的区别

在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。

引用和指针的不同点:

  1. 引用在定义时必须初始化,指针没有要求(但是最好初始化)

  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

  3. 没有NULL引用,但有NULL指针

  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)

  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

  6. 有多级指针,但是没有多级引用

  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理,我们直接使用引用变量即可访问

  8. 引用比指针使用起来相对更安全,指针使用起来更复杂、更不安全,指针要考虑空指针、野指针等。

void fun(int& a)
{
	a = 20;
}

void f(int* p)
{
	*p = 100;
}

int main()
{
	//fun(0);//错误,形参是int&,但是0是const类型
	//fun(NULL);//错误,没有空引用
	int i = 10;
	fun(i);

	//f(NULL);//空指针不能进行解引用操作
	//f(0);//0地址空间(也是空指针)我们没有权限访问

	return 0;
}

extern “C”

有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern “C”,意思是告诉编译器,将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用这两个接口,那么他就使用extern “C”来解决。

extern "C"只有C++能够认识,C语言不认识,所以extern "C"只能出现在C++代码中。

C++程序 调用C库,在C++程序中加"extern C"
C程序 调用C++库,在C++库中加extern “C”

内联函数

内联的概念

为了解决C语言宏使用比较麻烦的问题,C++引入了内联函数。

以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。

如果在上述函数前增加inline关键字将其改成内联函数,在编译期间,编译器会用函数体替换函数的调用。

查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进行优化,以下给出vs2013的设置方式)

总结】:长函数和递归函数不适合展开,展开后程序可能会变的很大。

特性

  1. inline是一种以空间换时间的做法,省去调用函数的开销。所以代码很长或者有循环/递归的函数不适合作为内联函数(短小的、频繁使用的函数建议设置成内联)。

  2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联。

3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

宏的优缺点

优点:
1.增强代码的复用性。
2.提高性能。

缺点:
1.不方便调试宏。(因为在预编译阶段,宏就进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。

C++有哪些技术替代宏?

  1. 常量定义 换成用const
  2. 函数定义 换成用内联函数
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-12-08 13:38:17  更:2021-12-08 13:39:47 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 12:00:23-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码