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++Prime Plus(2) -> 正文阅读

[C++知识库]C++Prime Plus(2)

21.for循环(1)

for循环作用:指定某段程序执行指定次数

格式:

for(计数器赋初值; 检查是否达到指定次数; 修正计数器值)
{
	计算过程;
}

fig1
我们使用for循环在计算机中的执行步骤为:
fig2
通常,for循环用在循环次数确定的场景中。

使用for访问字符串:字符串可以用字符数组,string类或指向字符的指针表示。
fig3
注意,虽然VS中写法为const char* str,但我们将鼠标移动至str变量,它的类型显式其实为const char *str,所以确实是pointer to const。

在C++11中,为了处理循环变量无规律变化的情况,新增了范围for;
格式:for (循环变量 : 数组或列表)
fig4

22.for循环(2)

在for循环中,常用 + +运算符和 - -运算符,这是两个一元运算符;

前缀:++k
后缀:k++

两个运算符有前缀和后缀区别:表达式的值不同,前缀的表达式结果是运算对象本身,后缀的表达式是修改前的运算对象值;
fig5
C++中,任何一个二元运算符,只要形式为:”变量=变量 op 表达式”,都可以简写为:“变量 op=表达式”,op是二元运算符。
比如:x*=5等价于x=x+5

逗号表达式:在只允许出现一个表达式的地方放多个表达式。
格式:表达式1,表达式2,…,表达式n
执行过程:依次执行表达式1,表达式2,…,整个表达式的执行结果是表达式n的值。
示例如下:
fig6
for循环中的关系表达式
作用:通过比较进行判断
格式:表达式1 关系运算符 表达式2,关系表达式的返回值:bool类型
fig7
当一个表达式出现多个关系运算符,按左结合性,C++先执行左边再执行右边。

字符串比较
C++风格的字符串比较:直接用关系运算符比较大小。
C风格的字符串比较:用cstring库中的函数strcmp。
fig8
举例:const char *s1=”abcde”; const char *s2=”aaaaaab”,在比较时,先比较第一个字符,两个都是a,比较下一个字符,s1中是b,值大于s2中的a,所以s1大于s2,应该返回正整数。

23.while循环

当循环次数不确定时,我们通常使用while循环。

格式:

while (测试条件)
	{循环体}

fig9
示例:显式C风格字符串中每个字符和对应的内码。
fig10
在上面示例中,我们使用cin将字符串输入到char型数组中。


对于输入一个单词:
cin>>字符数组名,比如cin>>str,cin以回车字符或者空格字符作为输入结束的标记

对于输入一行:

cin.getline(字符数组名,数组规模)
cin.get(字符数组名,数组规模)

以回车字符或达到数组规模结束输入,区别:getline将回车的换行符丢弃,get会将换行符留在缓冲区放在下一次输入的最开始位置。


上面示例,我们用while循环访问字符串效率高于使用for,因为for循环需要结束条件(需要计算字符串长度strlen(),其中strlen()函数其实是已经遍历了一次字符串才知道长度的,所以for循环相当于遍历字符串两次,而while则是一次);

示例:回显输入的字符并统计输入的字符数(进一步认识cin和输入队列或输入缓冲区
fig11
过程分析:当我们用键盘输入字符串时,实际上都是不断向输入队列输入数据。只有当我们键入回车符,C++才会向输入队列读取数据。(所以我们在回车前,我们可以在控制台删除输入错误的字符,再重新输入);

因此在上面示例中,我们虽然已经输入了#,但是由于没有回车,我们还可以输入后面的字符串。

当我们回车后,程序开始读输入队列,我们使用cin去读输入,cin的特点是以空格符和回车符作为每次的分隔(cin每次读到空格或者回车都会自动过滤这两种字符)。因此cin读到char中的字符是不会存在空格符和回车符的。当cin读#并存储到ch变量后,while循环结束。


完整回显式的解决方案:
用get函数,注意原型是cin.get(char),可以读入空格符和回车符;
不是cin.getline(字符数组名,数组规模)
不是cin.get(字符数组名,数组规模)

因此,修改为:
fig12


由于#可能是文本中本来存在的字符,因此,我们更需要一个键盘上没有的字符作为结束符。
我们应该用文件结束符EOF作为结束标记。
EOF是一个符号常量,表示文件结束。就像Linux的一切皆文件,C++的控制台可以看作文件,键盘是输入文件,显示器是输出文件。因此读键盘(其实是读输入队列)就是在读文件。当读到EOF时,就结束。

EOF的输入:Unix系统:Ctrl+D,Dos系统(Win):Ctrl+z
在C++中,当cin.get(char)读到EOF后,cin.fail()会返回true,因此,我们有以下示例:
fig13
再次强调cin.get(char),cin.get(char)其实已经把回车符读入了,因为在控制台上可以看到,回显字符串后我们输入EOF时已经在下一行,说明回显已经包含了回车符。
另外,观察到Ctrl+z与36 chars read之间空了一行,那一行其实是我们输入Ctrl+z后键入回车符导致的。键入那个回车符代表光标已经来到下一行,但是cout<<count之前,我们cout了endl,所以光标又下移一行。因此出现了空一行的现象。

24.do while循环

先执行循环体,再判断循环条件表达式。格式:

do
	{循环体}
while(测试条件);

fig14
示例:输入并回显字符,直到遇到#,输出#并输出字符数(包含#
fig15

25.二维数组与嵌套循环

二维数组:一维数组的数组
定义:类型名 数组名[常量1][常量2],常量1是一维数组的数量,常量2是一维数组的元素数。比如int a[3][4]
fig16
二维数组的初始化和元素表示:
fig17
二维数组的访问:用两层嵌套的for循环
fig18
三种字符串数组的表示:
fig19

26.if语句

第一种:if 后为then子句;
例如:统计输入字符中的空格数和总字符数
fig20
注意回顾缓冲区的原理,每次换行后,键盘输入的字符串被送到缓冲区,程序读缓冲区的数据并执行各种功能。

第二种:if条件不满足时,由else子句执行;
例如:将输入字符回显成字母表的下一字符:
fig21
第三种:if语句的嵌套,if语句的then子句或else子句是if语句
fig22

27.逻辑表达式

前面的那些表达式:比较谁大谁小,是否相等,这些是关系表达式。为了处理更复杂的条件,我们应使用逻辑表达式。
逻辑运算符:与&&(二元运算符) 或||(二元运算符) 非!(一元运算符)
真值表为:
fig23
fig24
结合性为左结合,先计算左边。

对于逻辑表达式的运算对象:0表示false,非0表示true,不同于bool类型。


bool类型:表示逻辑”真”和”假”,bool类型的值(true和false)
bool类型的机器内表示为一个字节,true是1,false是0,bool可以作为算术运算的运算数。
bool不能直接输入输出,直接输出bool类型的值得到的不是true或flase,而是1或0。


注意以下次序问题:a为非0,即true,对于||,有一个为true,结果就为true,所以后面的b+=a就不会计算,因此b不是8而是3。
fig25
示例:避免输入的整数超出合法范围
fig26

28.条件表达式

fig27
fig28
示例:输出两个整数的最大者
fig29

29.switch语句

if语句和条件表达式只有两个分支,为了处理多个分支,我们应该使用switch语句
fig30
break语句与多分支,break可以跳出当前的switch语句
fig31
switch与break配合使用才能实现多分支

示例:菜单选择
fig32
break与continue
fig33

30.文件概念

文件是存储在外存储器中的数据集合,程序运行时可以从文件中获取信息,不一定要从键盘输入。

C++将文件看成是一串流动的数据,称为数据流,从外围设备流入程序的数据称为输入流,从程序流向外围设备的数据称为输出流,每个数据流表示为一个对象。

C++将控制台输入输出看成是一个文本文件:
fig34
文件类fstream,用于文件访问,需要包含头文件fstream,之后:
读文件:定义ifstream类的对象;
写文件:定义ofstream类的对象;
读写文件:定义fstream类的对象;

比如:
fig35

31.文本文件的输入输出

根据上一节的内容,我们需要遵循以下流程:
1.包含头文件fstream
2.定义一个文件流对象
3.将对象与被访问的文件关联起来
4.从文件读取数据
5.关闭文件

示例:写文件
fig36
读文件示例:从文件读取一组实数,计算总和与均值
fig37
注意到inFile.fail(),其实inFile就像cin,我们用inFile向内存读入文件中的内容,即输入队列中的逐个对象读入value,由于value是double型,当遇到非数值的对象时,inFlie.fail()返回true 1。

32.函数详解(1)回顾

函数定义:
fig38
函数原型声明:让编译器检查函数调用的正确性;
fig39
函数调用:
fig40
函数示例:
fig41

33.函数详解(2)参数传递

C++中,参数传递的默认方式为值传递(将数值传给函数)

函数中创建形式参数,为形式参数分配空间,将实际参数作为初值。
fig42
对于多个参数,实际参数间用逗号分开。
fig43
实际参数可以是一个表达式,比如:
fig44
示例,计算m^n
fig45

34.函数详解(3)数组传递

数组传递:函数的参数是一组同类变量;
数组传递需要两个参数:数组名,长度;
比如计算一组整数的和:int sum(int a[], int size);

示例:
fig46
注意实际参数传递时:sum_arr(cookies, ArSize); 数组名字是数组首元素的地址,数组名是指针常量,
因此,数组传递其实传递的不是数组,而是地址(等价于sum_arr(&cookies[0], ArSize);),所以我们可以用指针作为函数的参数。

数组名是一个指针,数组传递可以用指针表示:
int sum_arr(int* arr, int n);
实际参数也可以表示成指针:
int *p=&cookies[0]; 或者int *p=cookies;
sum=sum_arr(p, ArSize);

用指针形式使得数组参数的处理更灵活,比如我们要求从第3个元素开始的元素之和:

int *p=cookies+2;
sum=sum_arr(p, ArSize);

数组传递中传递的是数组的起始地址,不需要像值传递中需要为所有对象开辟空间,所以数组传递更节省空间;

示例:输入,修改,显示数组
fig47
注意到值的问题:
fig48
可以发现,在值传递时,经过调用函数,实际参数在函数退出后并未被修改,但在数组传递时,调用函数退出函数后,数组内容被修改了。

实际上,函数的参数传递是将实际参数拷贝到形式参数上,由于数组传递拷贝的是地址,这导致地址下的内容确实被修改了,而值传递就不会受到影响,被修改的只是拷贝的值,在函数退出后,该拷贝值就自动被栈回收了。

35.函数详解(4)C风格字符串

C风格的字符串是用字符数组表示,注意最后有结束符’\0’

字符串传递,由于有结束符这个特点;
我们只需要一个参数,字符数组名 或者 指向字符的指针。函数从数组名中的地址开始一直处理到’\0’

示例:
fig49
字符串也可以为返回值,返回一个指向字符的指针。

注意指针指向的空间必须是离开函数后依然可用的,该空间不应该来自局部自动变量,所以我们通常用动态内存来实现

因为,如果只返回一个指针,我们只能返回该指针的地址,函数退出后,地址对应的内容已经被回收(如果是局部自动变量)。

示例:
fig50

36.递归概念

适用条件:求解一个问题时,需要用到同类问题的解。此时可以通过调用自身获得同类问题的解。比如汉诺塔问题。
fig51
递归函数:调用自身的函数

递归函数必须满足两个特点:1.调用自身;2.有终止条件。

递归解决汉诺塔问题:假设有一个function可以实现将n个盘从A经过B移动到C;
1.n-1个盘从A经过C移动到B;(调用function,改变参数位置,注意问题规模已经缩小到n-12.第n个盘直接从A移动到C;
3.n-1个盘从B经过A移动到C;(调用function,改变参数位置,注意到问题规模已经缩小到n-1)
注意:我们要加上终止条件。

递归函数的示例:
fig52
该示例演示了函数从栈申请内存的过程,逐层调用,最后回溯归还内存,以下图解方便理解:
fig53
递归与倒序
递归函数执行时,最先完整执行的是最后一次调用的函数,所以递归常用于倒序处理;
例如,输出一个十进制数n的二进制表示:
初始想法:依次输出二进制的第一位,第二位,…,最后一位
问题:如何得到第一位;
提示:得到最后一位很方便,奇数为1,偶数为0;
解决方案:先以二进制输出n/2,再输出最后一位。
fig54

37.函数指针

前面我们知道,对于变量,我们可以通过变量名直接访问,也可以利用指针间接访问。
事实上,指针不仅能保存变量的地址,也可以保存函数的地址。
保存函数地址的指针被称为函数指针。

定义指向函数的指针:

返回类型 (*指针变量名) (形式参数列表);

注意:我们必须在 *指针变量名 加括号,不然编译器会将该声明视为函数返回的值是指针。
fig55
我们可以将函数指针作为函数名使用,也可以用*运算符来间接调用函数;

为什么我们要这么麻烦地用指针间接调用函数?因为我们可以将指向函数的指针作为函数的参数。

例如:
fig56
fig57
调用时,可以用(*pf)(lines),也可以用 pf(lines)
总结:指向函数的指针用于将函数作为另一个函数的参数。

38.内联函数

在C语言发展到C++后,新增了一类函数,叫内联函数。
内联函数:有函数的形式,但是没有函数调用的代价,我们知道,函数调用存在代价,A函数调用B函数时,先把A暂停,将实际参数拷贝到形式参数,然后执行B,再返回值,继续执行A,可以看到,函数调用会产生额外的代价。

针对简单的函数,传统的调用反而开销相对过大,所以我们应该使用内联函数。

格式:inline 返回类型 函数名 (形式参数列表);
内联函数的定义必须出现在函数调用之前。

示例:
fig58

39.引用变量

引用变量实际上是变量的别名;
引用变量声明:

类型 &变量名=已有的同类变量;
如:int k;
    int &j=k;

用途
对引用变量操作其实是在对被引用变量进行操作。
因此,我们可以将引用变量作为函数的参数,使形式参数是实际参数的别名,从而代替指针传递,使得函数可以修改到实际参数。

引用传递的示例:
fig59
引用传递会修改实际参数,如果想让引用传递代替值传递,需要在形式参数前面加const限定

引用返回(返回一个引用变量)
格式:

类型& 函数名 (形式参数表);

函数最后返回的必须是一个变量名(所以不可以返回非变量名的表达式);
作用:
将函数用于赋值运算符的左边,即作为左值。
比如:
fig60
注意,返回值必须是离开函数后依然存在的左值,如果在返回类型前加const,此时函数不能作为左值。

40.函数参数的默认值

对于某些函数,程序往往会用一些固定的值去调用它;
例如对于以某种数制输出整型数的函数print:void print(int value, int base);
在大多数情况下都是以十进制输出,因此base值总是10;

C++在定义或声明函数时可以为函数的某个参数指定默认值,当调用函数时没有为它指定实际参数时,系统自动将默认值赋给形式参数。
fig61
默认参数的好处,使函数应用更灵活,方便
fig62
默认参数的示例:
fig63
注意函数参数的默认值是在原型声明中指定的,编译器根据原型检查函数调用的正确性,不同的源文件可以指定不同的默认值,使函数使用更加灵活。

默认值参数都必须放在参数列表的最后。

41.函数重载

一组同名函数,比如:
fig64
函数特征标:函数的参数数目,类型,及排列顺序,
对于重载函数,必须有不同的函数特征标。
fig65
示例:
fig66
fig67

42.函数模板(1)定义与使用

当我们处理不同相似类型的数据时(比如对int和double都只要执行相同的处理过程),结果需要写两个重载函数,比较麻烦,所以C++提出函数模板;

在函数模板中,用参数表示函数中的变量类型,调用时,用具体的类型代入,形成一个真正的函数。

作用:实现通用函数
fig68
在写函数模板时,占位符T可以由template<typename T>声明,
也可以由template<class T>声明。

示例:
fig69
显式地实例化
使用函数模板时,为了强制明确参数与返回值类型,我们可以在调用时,在函数名后面明确指出模板的实际参数:

函数名<模板实际参数表>(函数的实际参数表);

fig70
在C++11中,我们可以使用尾置返回类型,让编译器自动推断返回类型
格式:

auto 函数名 (形式参数列表)-> decltype(表达式)

比如:
fig71

43.函数模板(2)函数模板的重载

函数模板的重载:一组同名的函数模板,注意重载函数模板必须有不同的函数特征标;
fig72

44.函数模板(3)模板的具体化

函数模板是泛型编程(处理过程相同,只是数据类型不同)的实现方式。

有时候,为了缩小泛型编程的范围,我们可以在定义函数模板时,就对模板进行具体的实例化(模板特化)。

编译器在调用函数时:
先检查是否是普通函数;
再检查是否是模板特化函数;
再检查是否是普通的模板。

示例:
fig73
fig74
对于上面的场景,当没有重载要求时,我们也可以用普通模板实现结构的成员交换:
fig75

45.多文件程序

之前的程序都是仅基于一个源文件,实际项目通常很大,我们应该使用多文件程序。

程序是函数组成的,函数数量不多时,可以用一个源文存放。函数数量很多时,可以分别放在多个文件中。

文件分类
源文件:函数定义;
头文件:程序中声明的结构类型,符号常量,函数原型;

每个源文件包含一组函数,源文件划分通常遵循以下习惯:
main函数单独放在一个源文件中;
功能类似的函数放在一个源文件;
关系比较密切的函数放在一个源文件。

多个源文件的程序,如果一个源文件A中的函数需要调用另一个源文件B中的函数f,需要在A中声明函数f;
解决方案:使用头文件,各个源文件可包含此头文件
fig76
示例:
fig77
自定义的头文件用include " "

注意到两个cpp都包含了头文件,在链接两个源文件时,会出现重复声明的情况,为了避免重复声明,我们需要使用头文件保护符:
fig78
意义是,当声明过时,标识符成立,比如#ifndef代表标识符还未定义,则我们#define标识符,于是执行#endif前面的声明。

事实上,在避免包含多次声明问题上,常有两种做法:

方式一:
#ifndef  __SOMEFILE_H__
#define   __SOMEFILE_H__
 ... ... // 声明、定义语句
#endif

方式二:
#pragmaonce
 ... ... // 声明、定义语句

46.多文件的编译和链接

在不同环境下,有不同的方式实现编译和链接。

在命令行界面下,通常我们使用:
编译命令 一组源文件名

比如在UNIX中,如果安装了C语言编译器cc,现在一个项目包含两个源文件,我们可以用以下方式编译链接:

cc file1.cpp file2.cpp

fig79
如果在IDE(集成开发环境),比如Visual Studio中,则可以通过RUN直接实现编译链接和运行。

47.变量的作用域

C++中,变量有两个性质:作用域存储持续性

作用域:程序中可以使用该变量的区域,可以是一个复合语句,函数,源文件或整个程序;

存储持续性:变量在内存中保留多长时间

注意区分两个特性:作用域只是描述程序可以使用变量的区域,持续性是描述变量在内存中何时被回收。

作用域分为两种:块作用域文件作用域

块作用域:一个复合语句内或函数内部声明的变量,包括函数的形式参数,从定义开始到该复合语句或函数结束。

文件作用域:声明函数外的变量,从定义开始到文件末尾有效。


全局作用域 或 程序作用域:可以被本程序的其他函数或文件使用的文件作用域变量;


作用域是程序的一个区域,一般来说有三个地方可以定义变量:
? 在函数或一个代码块内部声明的变量,称为局部变量。
? 在函数参数的定义中声明的变量,称为形式参数。
? 在所有函数外部声明的变量,称为全局变量。

作用域示例:
fig80

48.变量的存储持续性(1)-自动持续性

存储持续性描述变量在内存中保留的时间。
存储持续性分为两类:自动静态(程序执行结束才回收)

fig81
块和函数中声明的变量(包括形式参数)默认都是自动变量;
可以显式地用关键词auto或register声明;
比如:

int x; 
auto int x; 
register int x; //希望变量在寄存器中,可以加快读写速度

在定义时生成,所在块或函数执行结束后被回收;

循环或if语句即使没有括号也是一个块,也可以定义自动变量;
比如:for(int k=0; k<10; ++k) cout<<k<<endl;

示例:
fig82

49.变量地存储持续性(2)-静态持续性

静态变量:当程序运行结束变量才被回收,静态变量一旦生成,在整个程序运行期间都存在。

对于静态变量,如果定义时没有指定初值,编译器会将值设为0;

静态变量有三种:
1.外部链接
2.内部链接
3.无链接

外部链接
外部链接也称为外部变量,整个程序的所有函数都可以使用。变量只能定义一次,但可以在多处声明
fig83
链接全局变量时的声明,需要加extern,注意到,在这个多文件例子中,我们没有包含头文件就能使用f()函数,因为我们已经在file1中声明了file2的函数(头文件只是在链接时把声明语句链接到了file1中)。

内部链接
只有本文件中的函数可用。定义在文件开头,用static关键字说明,比如:static int x;
fig84
无链接
函数或块中用关键字static修饰的变量。
首次进入函数或语句块时生成,函数执行结束后并不回收,下次进入函数时也不重新生成,依然在原有空间下
fig85
补充
cin.getline(字符数组名,数组规模)
cin.get(字符数组名,数组规模)
以回车字符或达到数组规模结束输入,区别:getline将回车的换行符丢弃,get会将换行符留在缓冲区放在下一次输入的最开始位置。

cin.get的重载:
无参数的cin.get(),cin.get()读入任意一个字符,包含回车。
在早期C语言没有getline时候,只能使用get,但是get对于读入回车的处理会让人们对字符文本的逻辑容易出错,为了让get每次都只输入一行,并让回车不放在下一次输入的行中。
cin.get(char),可以读入空格符和回车符。

50.命名空间

名字空间的用途:防止名字的冲突。
名字包括:变量名,函数名,结构名,类名等。

局部变量:不同的函数可以有同名的局部变量。

成员名:不同的结构,类中可以有同名的成员。

不允许同名的情况:全局变量,函数,类。

名字空间:声明名称的区域,一个名字空间中的名字不会与另一个名字空间中的名字冲突。

创建名字空间:

Namespace 名字空间名 { 名字声明 }

比如:
fig86
名字空间的引用:

名字空间名 :: 名字

比如:
fig87
using声明

using 名字空间 :: 成员;

作用:将该成员加入到当前作用域,在此作用域中引用该成员不需要加名字空间名的限定。
比如:
fig88
using编译指令

using namespace 名字空间名;

作用:将该名字空间的成员加入到当前作用域,在此作用域中引用该名字空间的成员不需要加名字空间名的限定。
比如:
fig89
示例:
fig90

  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-05-08 07:51:17  更:2022-05-08 07:52:05 
 
开发: 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/23 20:22:25-

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