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++】基础:常见字符串与内存操作库函数

【C/C++】基础:常见字符串与内存库函数

摘要:在本篇博客中将介绍各种常见的字符串与内存操作库函数,希望通过查询资料,理解掌握库函数并且实现各种库函数,达成可以自主学习使用查询陌生库函数的目的。同时还需要注意各种每个库函数之间的细节。所有的资料查询来自:cplusplus.com - The C++ Resources Network


一. 字符串函数

字符串函数针对于字符串而言,在C语言中存在一个名为 string.h 的库,其中包含了对字符串的各种操作。而在C语言中,不存在字符串类型,因此常会把字符串存储于字符数组与常量字符串中

1.1 strlen

//函数定义与实现
size_t strlen ( const char * str );
size_t __cdecl strlen (
        const char * str
        )
{
        const char *eos = str;

        while( *eos++ ) ;

        return( eos - str - 1 );
}

作用:获取字符串的长度

返回值:字符串的大小

解释:传入字符串首地址,遍历字符串直到访问到 ‘\0’ 停止访问并返回字符串长度(不包含 ‘\0’)

注意事项:返回类型为 size_t ,为无符号整数,没有负数

使用样例:

/* strlen example */
#include <stdio.h>
#include <string.h>
int main ()
{
  char szInput[256];
  printf ("Enter a sentence: ");
  gets (szInput);
  printf ("The sentence entered is %u characters long.\n",(unsigned)strlen(szInput));
  return 0;
}
//Enter sentence: just testing
//The sentence entered is 12 characters long.

模拟实现:在此通过简单的循环遍历完成对数组的访问,根据来自资料显示的内容,访问到 ’\0’ 时返回字符串长度,注意不包含’\0’。最后的返回值,通过指针的运算来实现,注意指针进行计算时,其值为指向自身类型的数量。

size_t _strlen(const char *str) {
    assert(str);
	const char* ptr = str;
	while(*ptr!= '\0'){
		ptr++;
	}
	return ptr - str;
}

1.2 strcpy

//函数的定义
char * strcpy ( char * destination, const char * source );

作用:将源字符串拷贝到目的字符串

返回值:目的字符串的首地址

解释:将源字符串’\0’ 以前的内容拷贝到目的字符串中,包括 ‘\0’

注意事项:①源字符串必须以 ‘\0’ 结束;②会将源字符串中的 ‘\0’ 拷贝到目的字符串各种;③目标空间必须足够大,以确保能存放源字符串;④目标空间必须可修改;

使用样例:

/* strcpy example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str1[]="Sample string";
  char str2[40];
  char str3[40];
  strcpy (str2,str1);
  strcpy (str3,"copy successful");
  printf ("str1: %s\nstr2: %s\nstr3: %s\n",str1,str2,str3);
  return 0;
}
//输出:
//str1: Sample string
//str2: Sample string
//str3: copy successful

模拟实现:根据解释内容,该函数是通过 ‘/0’ 作为终止条件的,因此同样只需要通过循环遍历,不断复制直到 ‘/0’ 为止。还需要注意的是要将 ‘/0’ 也复制过去。当然还需要注意返回类型,是目标字符串的指针,因为需要不断遍历,因此我们要将其提前保存,以下是代码实现:

char* _strcpy(char* destination, const char* source) {
	char* return_ptr = destination;
	while ((*destination = *source) != '\0') {
		destination++;
		source++;
      
	}
	return return_ptr;
}

1.3 strcat

//函数定义
char * strcat ( char * destination, const char * source );

作用:将源字符串最佳到目的字符串中

返回值:目的字符串的首地址

解释:将源字符串追加到目标字符串。目标中的终止空字符被源字符串的第一个字符覆盖,并在目标中两者的连接形成的新字符串的末尾添加一个空字符。

注意事项:① 无法追加自身,因为当追加第一个字符后,终止空字符会被覆盖,这样便无法结束追加。如果追加自身可以使用后续将会提到的 strncat 函数;② 源字符串必须以 ‘\0’ 结束;③ 目标空间必须有足够的大,能容纳下源字符串的内容; ④目标空间必须可修改;

使用案例:

#include <stdio.h>
#include <string.h>

int main (){
  char str[80];
  strcpy (str,"these ");
  strcat (str,"strings ");
  strcat (str,"are ");
  strcat (str,"concatenated.");
  puts (str);
  return 0;
}
//输出
//these strings are concatenated.

模拟实现:同样通过解释内容开始分析,首先寻找到终结空字符,然后再通过循环逐步追加,直到达到源字符串的终止符为止。还需要注意返回类型,需要提前保存目的字符串的首地址,代码如下:

char* _strcat(char* destination, const char* source) {
	assert(destination);
	assert(source);
	char* ret = destination;
	while (*destination) {
		destination++;
	}
	while (*destination++ = *source++) {
	}
	return ret;
}

1.4 strcmp

//函数定义
int strcmp ( const char * str1, const char * str2 );

作用:比较两个字符串

返回值:返回值大于0,则第一个不匹配的字符在str1中的值大于str2中的值;返回小于0,则第一个不匹配的字符在str1中的值小于str2中的值;返回值为0,两个字符串相等。

解释:函数从第一个字符串开始比较。如果它们相等,则继续往后比较,直到字符不同或到达一个终止的空字符为止,具体结束条件可以查看返回值的要求。

注意事项:函数执行字符的二进制比较,需要考虑特定于语言环境规则的函数。

使用案例:

#include <stdio.h>
#include <string.h>

int main (){
  char key[] = "apple";
  char buffer[80];
  do {
     printf ("Guess my favorite fruit? ");
     fflush (stdout);
     scanf ("%79s",buffer);
  } while (strcmp (key,buffer) != 0);
  puts ("Correct answer!");
  return 0;
}
//输出
//Guess my favourite fruit? orange
//Guess my favourite fruit? apple
//Correct answer!

模拟实现:按照解释的内容,通过循环逐个比较内容,如果相等则继续往后比较,直到达到终止条件。

int _strcmp(const char* str1, const char* str2) {
	assert(str1);
	assert(str2);
	while (*str1 == *str2) {
		if (*str1 == '\0')
			return 0;
		str1++;
		str2++;
	}
	if (*str1 > *str2)
		return 1;
	else
		return -1;
}

1.5 strncpy

//函数定义
char * strncpy ( char * destination, const char * source, size_t num );

作用:拷贝num个字符从源字符串到目标空间。

返回值:目的字符串的首地址

解释:与strcpy相似,但是可以通过参数 n,来控制复制的数量

注意事项:①如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到达到num个;②目标地址和源地址不能重叠;

使用案例:

/* strncpy example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str1[]= "To be or not to be";
  char str2[40];
  char str3[40];

  /* copy to sized buffer (overflow safe): */
  strncpy ( str2, str1, sizeof(str2) );

  /* partial copy (only 5 chars): */
  strncpy ( str3, str2, 5 );
  str3[5] = '\0';   /* null character manually added */

  puts (str1);
  puts (str2);
  puts (str3);

  return 0;
}
//输出
//To be or not to be
//To be or not to be
//To be 

模拟实现:由于与strcpy类似,只需要更正循环次数和补充终止结束符即可。

1.6 strncat、strncmp

解释:strncat、strncmp 与strcat、strcmp的主要区别是增加了个参数num,可以通过num来完成对特定数量字符串 的操作。

注意事项:strncat可以完成对自身的追加

补充:strcpy、strcmp、strcat 与 strcnpy、strncmp、strncat 是两种不同类型的函数。前者归类为长度不受限制的字符串函数,后者归类为长度受限制的字符串函数。

1.7 strstr

//函数定义
const char * strstr ( const char * str1, const char * str2 );
      char * strstr (       char * str1, const char * str2 );

作用:查看 str1 中是否存在 str2 ,并返回寻找到的首元素的地址。

返回值:第一次找到相应字符串的首地址

解释:在一个字符串中另一个字符串是否存在,如果存在返回str2在str1中第一次出现的位置,不存在则返回空指针。

使用案例:

/* strstr example */
#include <stdio.h>
#include <string.h>

int main (){
  char str[] ="This is a simple string";
  char * pch;
  pch = strstr (str,"simple");
  if (pch != NULL)
    strncpy (pch,"sample",6);
  puts (str);
  return 0;
}
//输出
//This is a sample string

模拟实现:其实这个函数的实现方法有很多,这里为了解释方便,便使用最暴力的方法。首先要配对字符,通过两个遍历指针来指向两个字符串。在str1中用指针遍历,当找到后通过一个临时指针匹配,如果不匹配则返回到遍历指针,继续向后寻找,直到找到为止。如果找到,直接返回str1的遍历指针,如果未找到也可以返回str1的遍历指针,因为最后也是字终止结束符。以下是代码实现:

const char* _strstr(const char* str1, const char* str2) {
	//指针初始化
	const char* ptr_str1 = str1;
	const char* ptr_str2 = str2;
	const char* ptr_temp = ptr_str1;
	//循环:
	//条件:str1未遍历完
	//过度:ptr_str1往后移动
	for (;*ptr_str1 != '\0'; ptr_str1++) {
		if (*ptr_str1 == *ptr_str2) {
			//将临时指针相等处
			ptr_temp = ptr_str1;
			//不断的比较
			while(*ptr_str2 == *ptr_temp) {
				//相等就继续往后比较
				ptr_str2++;
				ptr_temp++;
				//如果str2遍历完,则返回str1的第一次相等的地址
				if (*ptr_str2 == '\0')
					return ptr_str1;
			}
			//如果匹配失败了,将ptr_str2初始化到初始位置
			ptr_str2 = str2;
		}
	}
	//匹配失败返回空指针
	return NULL;
}

1.8 stroke

//函数定义
char * strtok ( char * str, const char * delimiters );

作用:根据标记拆分字符串

参数:①delimiters参数是个字符串,定义了用分隔符的字符集合; ②第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标记;

返回值:根据标记拆分字符串,找到元素的首地址,如果找不到返回空指针

解释:①strtok函数找到str中的下一个标记,并将其用 ‘\0’ 结尾,返回一个指向这个标记的指针;②strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串中的位置;③strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记;④如果字符串中不存在更多的标记,则返回 NULL 指针;

注意事项:①字符串必须可修改,但一般会增加一个临时拷贝来完成操作;②留意第一个参数NULL的使用,在使用案例会通过注释完成说明。

使用案例:

void test_strtok() {
	//定义字符串
	const char* str = "fatone@csdn.com";
	//定义分隔符集合
	const char* sep = ".@";
	char arr[30];
	char* str_temp = NULL;
	//将数据拷贝一份,处理arr数组的内容
	strcpy(arr, str);
	//一般是通过循环完成分割
	//循环初始化内容为第一次分割,第一个参数为要分隔的字符串,返回首地址
	//如果返回值不为空,说明未找完,故继续分割,但第一个参数之后都要成为NULL
	//找到就返回首地址,继续循环,否则退出循环
	//注:每次分割完后,都会将分隔符置为'\0'
	for (str_temp = strtok(arr, sep); str_temp != NULL; str_temp = strtok(NULL, sep)){
		printf("%s\n", str_temp);
	}
}

1.9 strerror

//函数定义
char * strerror ( int errnum );

作用:返回错误信息

参数:错误编码

返回值:错误信息字符串的首地址

解释:通过错误编码返回错误信息

注意事项:①需要包含头文件 #include <errno.h>;②返回的指针指向静态分配的字符串,该字符串不能被程序修改;③strerror产生的错误字符串可能特定于每个系统和库实现;

使用案例:

/* strerror example : error list */
#include <stdio.h>
#include <string.h>
#include <errno.h>

void test_strerror() {
	//各种错误码类型
	printf("%s\n", strerror(0));
	printf("%s\n", strerror(1));
	printf("%s\n", strerror(2));
	printf("%s\n", strerror(3));
	printf("%s\n", strerror(4));
	//示例
	FILE* pFile;
	pFile = fopen("unexist.ent", "r");
	if (pFile == NULL)
		printf("Error opening file unexist.ent: %s\n", strerror(errno));
}

二. 字符函数

2.1 字符分类函数

作用:根据字符判断类型

解释:传入字符参数后,判断是否符合条件,如果参数符合返回为真,不符合返回为假

注意事项:包含头文件 #include <ctype.h>

函数作用(如果参数符合条件返回为真)
iscntrl任何控制字符
isspace空白字符:空格‘ ’,换页‘\f’,换行’\n’,回车‘\r’,制表符’\t’或者垂直制表符’\v’
isdigit十进制数字 0~9
isxdigit十六进制数字,包括所有十进制数字,小写字母af,大写字母AF
islower小写字母a~z
isupper大写字母A~Z
isalpha字母az或AZ
isalnum字母或者数字,az,AZ,0~9
ispunct标点符号,任何不属于数字或者字母的图形字符(可打印)
isgraph任何图形字符
isprint任何可打印字符,包括图形字符和空白字符

使用案例:

void test_ischar() {
	char ch = 'A';//'0' '1' '2' '3' .... '9'
	int ret = isxdigit(ch);
	printf("A isxdigit? %d\n", ret);
	ret = islower(ch);
	printf("A islower? %d\n", ret);
}

2.2 字符转换函数

作用:根据要求,对字符进行转换

解释:传入参数后,根据需求转换字符,转换后返回

注意事项:包含头文件 #include <ctype.h>

函数作用(返回转换后的结果)
tolower转换为小写
toupper转换为大写

使用案例:

void test_tochar() {
	char ch1 = 'W';
	char ch2 = 'w';
	printf("%c\n", tolower(ch1));//ch+32
	printf("%c\n", toupper(ch2));//ch-32
}
//输出:
//w
//W

三. 内存操作函数

3.1 memcpy

//函数定义
void * memcpy ( void * destination, const void * source, size_t num );

作用:内存块复制

返回类型:拷贝后的目的内存块的首地址

解释:函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置

注意事项:①不同于strcpy,这个函数在遇到 ‘\0’ 的时候并不会停下来;②如果source和destination有任何的重叠,复制的结果都是未定义的,如果出现会有重叠的问题,最好使用将会提到的memmove函数;③要防止溢出的问题;④当num的字节数大于source时,就会复制终止结束符。

使用案例:

/* memcpy example */
#include <stdio.h>
#include <string.h>
struct {
  char name[40];
  int age;
} person, person_copy;
int main (){
  char myname[] = "Pierre de Fermat";
  /* using memcpy to copy string: */
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;
  /* using memcpy to copy structure: */
  memcpy ( &person_copy, &person, sizeof(person) );
  printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
  return 0;
}
//输出:person_copy: Pierre de Fermat, 46 

模拟实现:在此模拟实现,在参数中发现有空指针,大家都知道空指针可以防止任何指针内容。在获得需要拷贝的地址后,我们可以对最小字节单位进行复制,只需要将空指针转换为char类型指针,再不断拷贝即可。同样需要注意返回类型,由于是返回目的地址,在我们实现过程中会发生改变,因此提前用一个临时变量将其记载,最后返回。代码实现如下:

void* _memcpy(void* destination, const void* source, size_t num) {
	assert(destination);
	assert(source);
	//返回指针定义
	void* return_ptr = destination;
	//复制num个字节,循环进行num次
	for (size_t i = 0; i < num; i++) {
		//复制一个字节
		*(char*)destination = *(char*)source;
		//复制完后向后推一个字节
		destination = (char*)destination + 1;
		source = (char*)source + 1;
	}
	return return_ptr;
}

3.2 memcpy

//函数定义
int memcmp ( const void * ptr1, const void * ptr2, size_t num );

作用:比较从ptr1和ptr2指针开始的num个字节

返回值:返回值大于0,则第一个不匹配的内存块在ptr1中的值大于ptr2中的值;返回小于0,则第一个不匹配的内存块在ptr1中的值小于ptr2中的值;返回值为0,两个内存块相等。

解释:函数从第一个字符串开始比较。如果它们相等,则继续往后比较,直到字符不同或到达一个终止的空字符为止,具体结束条件可以查看返回值的要求。

注意事项:与strcmp不同的是,该函数在找到空字符后不会停止比较。

使用案例:

/* memcpy example */
#include <stdio.h>
#include <string.h>

struct {
  char name[40];
  int age;
} person, person_copy;

int main (){
  char myname[] = "Pierre de Fermat";
  /* using memcpy to copy string: */
  memcpy ( person.name, myname, strlen(myname)+1 );
  person.age = 46;
  /* using memcpy to copy structure: */
  memcpy ( &person_copy, &person, sizeof(person) );
  printf ("person_copy: %s, %d \n", person_copy.name, person_copy.age );
  return 0;
}
//输出:'DWgaOtP12df0' is greater than 'DWGAOTP12DF0'.

模拟实现:与strcmp相似,按照解释的内容,通过循环逐个比较内存块,如果相等则继续往后比较,直到达到num次。在此我们只需要主要对内存快点访问,通过char*指针来完成每个内存块的遍历。按以下代码逻辑,需要注意的的是循环次数num-1次即可,否则会指向后面的内存块。实现代码如下:

int _memcmp(const void* ptr1, const void* ptr2, size_t num) {
	assert(ptr1);
	assert(ptr2);
	for (size_t i = 0; i < num -1 ; i++) {
		if (*(char*)ptr1 == *(char*)ptr2) {
			ptr1 = (char*)ptr1 + 1;
			ptr2 = (char*)ptr2 + 1;
		}
		if (*(char*)ptr1 > *(char*)ptr2)
			return 1;
		if(*(char*)ptr1 < *(char*)ptr2)
			return -1;
	}
	return 0;
}

3.3 memmove

//函数定义
void * memmove ( void * destination, const void * source, size_t num );

作用:移动内存块的数据,将num个字节的数据从源内存块移动到目的内存块。

返回值:移动后的目的内存块地址

解释:与memcpy相似,不过和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。memmove仿佛在内存中有一个缓冲区一样,允许重叠。

注意事项:如果源空间和目标空间出现重叠,就得使用memmove函数处理;同时也需要防止溢出;

使用案例:

/* memmove example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "memmove can be very useful......";
  memmove (str+20,str+15,11);
  puts (str);
  return 0;
}
//输出:memmove can be very very useful.

模拟实现:memcpy之所以不能完成重叠访问,是因为重叠访问会使得原来存在的内存块被覆盖。在此为了简单实现memmove,我们使用一个较为巧妙的方法,使得在复制过程中,在未被复制之前,内存块内容不会被替换。

首先我们分析为什么会被覆盖,以将int str[30]为例,内容为字符串"abcdefghijklmno\0"。希望用memcpy函数拷贝自身将source = str,拷贝到destination = str + 3,拷贝7个字节,调用函数为:memcpy(str+3,str,7); 当拷贝第一个内存块中的内容时,将会改变后面需要改变的内容,这样就发生错误。但是如果我们从后面开始移动就不会出现相应问题。可是从后面移动也不能解决所有问题,因为,如果source = = str + 3,而destination = str 时也会出现覆盖问题,这时就需要从前开始移动,其示意图如下图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-F2LX90Yw-1663514865385)(D:/Blogs/C_C++/c–c–review/library%20_unction/memcpy_question.png)]

为此,我们只需要分情况讨论什么时候从前移动,何时从后移动即可。当发生重叠时,如果源内存块在目的内存块之后,就从前开始复制,如果源内存块在目的内存块之前,就从后开始复制。代码实现如下:

void* _memmove(void* destination, const void* source, size_t num) {
	void* ret = destination;
    //当目标内存块在源内存块之前
	if (destination <= source) {
		for (int i = 0; i < num; i++) {
			*(char *)destination = *(char *)source;
			destination = (char*)destination + 1;
			source = (char*)source + 1;
		}
	}
     //当目标内存块在源内存块之后
	else {
        //提前偏移到末尾
		destination = (char*)destination + num - 1;
		source = (char*)source + num -1;
		for (int i = 0; i < num; i++) {
			*(char*)destination = *(char*)source;
			destination = (char*)destination - 1;
			source = (char*)source - 1;
		}
	}
	return ret;
}

3.4 memset

//函数定义
void * memset ( void * ptr, int value, size_t num );

作用:填充内存块

参数:ptr:指向要填充的内存块的指针;value:要设置的值,该值作为int传递,但函数使用该值的无符号字符转换填充内存块;num:填充的字节数;

返回值:返回填充后的内存块的地址

解释:将ptr指向的内存块的第一个num字节设置为指定值(解释为无符号字符)。

使用案例:

/* memset example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] = "almost every programmer should know memset!";
  memset (str,'-',6);
  puts (str);
  return 0;
}
//输出:------ every programmer should know memset!

四. 方法分享

以上的函数都是笔者在cplusplus.com - The C++ Resources Network查找的,在此想为大家分享一下自己查资料的方式。在打开连接后,我们会发现其中有一个搜索引擎,我们可以通过此寻找到我们所需要的的库函数。在搜索完毕后,我会先从加粗的字体入手,这是函数的功能概述。之后才会从他的定义出发,分别查看参数与返回值,最后才会去看函数的解释。完成学习后,还需要手敲一遍示例,加深自我学习的印象。


补充:

  1. 代码将会放到: https://gitee.com/liu-hongtao-1/c–c–review.git ,欢迎查看!
    传递,但函数使用该值的无符号字符转换填充内存块;num:填充的字节数;
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-09-24 20:38:18  更:2022-09-24 20:39:42 
 
开发: 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年5日历 -2024/5/19 12:11:05-

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