收藏 分销(赏)

C语言基础学习笔记.doc

上传人:人****来 文档编号:4719322 上传时间:2024-10-10 格式:DOC 页数:34 大小:377.54KB
下载 相关 举报
C语言基础学习笔记.doc_第1页
第1页 / 共34页
C语言基础学习笔记.doc_第2页
第2页 / 共34页
C语言基础学习笔记.doc_第3页
第3页 / 共34页
C语言基础学习笔记.doc_第4页
第4页 / 共34页
C语言基础学习笔记.doc_第5页
第5页 / 共34页
点击查看更多>>
资源描述

1、C语言基础学习笔记(第一版修改)丁炳亮1数据类型和表达式1.1计算机内数据存储方式理解与测试:计算机内部所有的数据都是以二进制的形式存储的。我们平常使用的有理数都是分正负数的,在计算机内部整型是通过最高位区分正负数,负数的最高位为1。如果直接用这种表达方式进行运算,那么运算前要先判断是无符号数还是有符号数,因为有符号数的最高位是符号位不能直接参与运晕,并且减法运算还得有专门的减法器。为了能把符号位参与运算且加法和减法能够统一处理我们用到了补码。什么是补码?我们得先知道模的概念。模“模”是指一个计量系统的计数范围。如时钟等。计算机也可以看成一个计量机器,它也有一个计量范围,即都存在一个“模”。例

2、如:时钟的计量范围是011,模=12。表示n位的计算机计量范围是02(n)-1,模=2(n)。“模”实质上是计量器产生“溢出”的量,它的值在计量器上表示不出来,计量器上只能表示出模的余数。任何有模的计量器,均可化减法为加法运算。例如:假设当前时针指向10点,而准确时间是6点,调整时间可有以下两种拨法:一种是倒拨4小时,即:10-4=6;另一种是顺拨8小时:10+8=12+6=6在以12模的系统中,加8和减4效果是一样的,因此凡是减4运算,都可以用加8来代替。对“模”而言,8和4互为补数。实际上以12模的系统中,11和1,10和2,9和3,7和5,6和6都有这个特性。共同的特点是两者相加等于模。

3、二进制中整数的补码求法是:正数的补码为原码,负数的补码是符号位不变其他位全部取反再整个数加1。我们可以通过下面的代码看下负整数在计算机内部的表示。void f(int n) unsigned int i; for(i=1,i=1) if(i&n) printf(1); else printf(0); printf(n);main() int a=-0xff; f(a); getch();输出的结果是1111111100000001。1.2变量与常量理解与测试:1)类型声明在计算机内部数据是以字节为单位存储的,但是我们需要的数据类型有很多种,每种数据类型所占字节和存储方式都不一样。类型声明的作用

4、就是告诉计算机以哪种“格式”去读写该数据数据。 类型说明符 变量名1,变量名2.,变量名n;类型说明符有基本类型(字符型、整数型、单/双精度型)、高级类型(结构体型、共用体型、枚举类型)、指针型等,其中指针类型包括指向基本类型的指针类型、指向高级类型的指针型和指向函数的指针类型(指向函数指针声明格式在后面章节)。2)符号常量的定义#define 标识符 常量;使用该种方法定义符号常量一个是可以使代码便于阅读理解,另一个是方便代码内部多个相同常量的统一修改。总结与注意在写计算式的时候要考虑变量是否会越界。一般来说计算式子时是先强制转换成式子中最大存储空间的数据类型(不包括被赋值的变量),还要注意

5、不同的类型数据在不同的编译器中所占的内存有可能是不一样的,例如有些编译器整型是占2个字节有些是占4个字节。同时还要考虑到符号的优先级和结合顺序,如果按符号的优先级和结合顺序运算过程中有越界的那么整个计算结果可能和预想的不一样了,例如int i=100;i = 500*i/i;最后i=-155。1.3输出输入理解与测试:1)格式化输入输出函数可以按设定的格式和设定的数据类型接收和输出多个变量。控制格式和数据类型的是数据控制符。2)字符和字符串可以用专门的输出输入函数。主要有getch(); putch();getchar(); putchar(); gets(); puts();其中getch(

6、)和putch()是conio.h中声明,getch()不需要等待回车键就返回字符并立即执行下面一语句符,getch()不在屏幕显示输入的字符。getchar();putchar(); 在stdio.h中声明。getchar()读取键盘上的一个字符,立即返回并显示在屏幕上,需要等待回车键才能执行下一条语句。例如下面的代码:int main(void) char ch,i; for(i=0;i10;i+) ch = getchar(); putchar(ch); getch();输入:I LOVE YOU输出:I LOVE YOU总结与注意格式输入函数中的参数是变量存放的地址,所以变量前面要”&

7、”符号。如果是数组则可以直接用数组名,因为数组名就是指向该数组首地址的指针常量。1.4运算符号理解与测试:1)表达式中符号的运算顺序是有先后的,根据符号的优先级和结合性(左结合/右结合)来判定。如果是几个符号连在一起C内部是怎么去读的呢?有一个很简单的规则:每一个符号应该包含尽可能多的字符。也就是说,编译器读取符号的方法是,从左到右一个字符一个字符地读入,如果该字符可能组成一个符号,那么在读入下一个字符,直到与下个字符组合成的符号没有意义为止。例如:a-b;这个表达式等效于(a-)-b;2)复合赋值符及表达式在赋值符“=”之前加上其他双目运算符号可以构成复合赋值符。如:+=、-=、*=、/=、

8、%=。构成复合赋值表达式的一般格式为变量 双目运算符 = 表达式等价于变量 = 变量 运算符 表达式可以这么去理解,等号左边的相当于变量自增、自减、自除、自取余一个数,这个数由右边的表达式计算得到。如:int a=7,b=3,c=3;a+=b+=c+=5;运算后是a=18,b=11,c=8。3)C中唯一一个三目运算符是条件运算符,由“?”和“:”构成。两个符号必须按下面的格式一起使用:表达式 1?表达式2:表达式3三个表达式可以是任意的合法表达式,条件运算符的运算规则如下:如果表达式1的值为真,那么整个表达式的值为表达式2的值,否则为表达式3的值。4)sizeof()也是一个运算符号,它可以对

9、一个数据类型或一个变量取类型长度。但是当一个数组名作为一个实参传递给函数时,在函数内部是不能用sizeof来获取该数组的长度的。因为数组名是一个指向数组首地址的指针,使用sizeof只能取得实参指针类型长度。5)比较少用到的符号是“:”和“,”。冒号运算符在C中有三种用途,第一种是我们上面讲到的与“?”构成三目运算。第二种是在switch中放在常量表达式后面。第三种用法是在结构体中定义位域时用到。struct 位域结构名 位域列表 ;其中位域列表的形式为: 类型说明符 位域名:位域长度例如:struct bs int a:8; int b:2; int c:6;逗号运算符的功能是把两个表达式连

10、在一起组成一个新的表达式,称为逗号表达式。其一般格式为表达式1,表达式2,表达式3,.,表达式n;其求值过程是自左向右求两个表达式的值,并以最后一个表达式的值作为整个逗号表达式的值。例如for(a=0,b=0,c=0;b10,a20;b+,a+)+c;运算后c=20,如果把中间的两个语句前后调换for(a=0,b=0,c=0;b10,aj?i+:j; printf(%dn,n); getch();输出结果:243)在写式子时要注意运算符号对变量的副作用,单目运算有“+”和“-”,双目运算有“=”。4)位运算符号与逻辑符号容易混淆的有“&”和“&”、“|”和“|”、“”和“!”。前面两个可以这么

11、记,位运算是单个的所以符号也是单个的。5)在使用位运算符号时要主要如果是有符号数则是对他们的补码执行位运算。对于有符号的整数型使用左移“”左补1。如下代码:void f(int n) unsigned int i; for(i=1,i=1) if(i&n) printf(1); else printf(0); printf(n);main() int a=-0xff; f(a); a=1; f(a); getch();输出的结果是:111111110000000111111111100000006)运算符优先级可以简单归纳如下几点:单目运算符比任何一个真正意义上的运算符的优先级高。逻辑运算符比

12、关系运算符优先级高。移位运算符表算术运算符优先级低,但高于关系运算符。2程序控制结构2.1选择结构理解与测试:使用if-else多层嵌套时else是与上面同一层的最近if相结合的,在程序设计时有过多的分支最好要选择使用if-else,当然如果可以使用switch语句还是尽量选择switch语句或者使用其他能使代码简洁易懂的方法。如下面的代码:main() int a=10,b=6,c=7,d=8,e=9,f=10; if(ab) printf(%dn,a); if(bc) printf(%dn,b); if(cd) printf(%dn,c); if(de) printf(%dn,d); if

13、(ef) printf(%dn,e); else printf(%dn,f); getch();这种代码又长又难看懂,更要命的是经常搞不懂下面的大括号对应上面的哪个大括号。改进后的代码如下main() int a=10,b=6,c=7,d=8,e=9,f=10; while(1) if(a=b)break; printf(%dn,a); if(b=c)break; printf(%dn,b); if(c=d)break; printf(%dn,c); if(d=e)break; printf(%dn,d); if(e=f) printf(%dn,e); else printf(%dn,f);

14、break; getch();第二种方法比第一种方法来看上去更干净,理解上差不了多少。2)总结与注意switch语句中每一个分支结束都要有break;语句,如果没有是继续执行下面分支,可能会得到和预料不同的效果。还要注意,case后面跟的一定是整数型或符号型常量表达式,不能是变量,并且不用有相同值的变量表达式,不然会矛盾的。2.2循环结构理解与测试:1)主要有for、while、do-while这三种结构循环体,其中最后一种是比较少用到的,因为一般情况都可以用前面两种替代。2)循环体结构中括号的表达式要知道时候退出,有时候还要知道循环体运行多少次。在循环体内部也可以使用continue语句跳过

15、本次循环,进入下一次循环,或者用break语句退出当前层循环。总结与注意循环结构中表达式的设计很重要。在for循环结构我们经常用到从0到x,循环体执行x+1次,可以写成for(i=0;i0;i-=3);这个循环语句的运行结果一般不是我们想要的。把“=”不注意写成“=”也是容易造成死循环的结果。3数组3.1一维数组理解与测试:1)一维数组的定义如下:类型说明符 数组名常量表达式如果定义时就进行初始化,常量表达式可以省略。动态存储的数组没有初始化所有元素的值都为随机。静态存储的数据没有初始化所有的元素自动赋0。2)一维数组名是一个指针常量,指向数组的首地址。由于是指针常量所以不能对其赋值,只能当指

16、针引用。同理一个数组不能对另一个数组整体赋值。数组引用时下标是从0开始计算的。3)字符型数组初始化可以直接赋值字符串。char s=“Happy”;或者char s=”Happy”;。总结与注意数组引用时下标不要越界,字符串数组初始化时,数组元素个数要比字符串多一个,因为字符串结束有个“n”符,否则越界就会发生预料不到的结果。如下:main() char a8=abcdefgh; printf(%s,a); getch();输出结果:main() int a2,i; for(i=0;i8;i+) scanf(%d,&ai); getch();上面这个代码当输入字符超过2个时可能出现无法运行的结

17、果。总结与注意数组名作为函数参数时传递给形参的是数组的指针,而且在子函数中无法用sizeof取得数组的大小,所以有需要时可以用另一个参数传递数组的大小。数组作为形参时可以写成int f(int a)或者int f(int * a)或者int f(int a10),他们三者都一样的,因为对于指针来说“”等效于 “*”,数组做形参不会传递数组大小,int a10等效于int a。数组作为实参时可以写成f(a)或者f(&a0)或者,因为a0的地址就是数组的首地址。但是要注意f(a10)将是把a10作为实参传递过去,a10的值赋值给指针使用是危险的。3.2多维数组理解与测试:多维数组和一维数组相似,只

18、是使用了多个下标。多维数组其实可以理解为数组的元素又是一个数组。例如,二维数组可以保存一个矩阵,三维数组可以保存多个矩阵。4函数4.1函数的定义和调用理解与测试:1)函数定义一般格式如下函数类型 函数名(形参表) 声明部分; 语句部分; return 表达式; 声明部分必须是放在函数的最前面,如果放在语句中则编译不能通过。2)值参数是指,将实际参数的值传递到函数的形式参数中,这也是参数默认传递方式。函数调用中发生的数据传递是单向的,即只能把实际参数的只传递给形式参数,在函数调用过程中,形式参数发生改变,而实际参数中的值不会发生变化(形式参数可与实际参数同名)。引用参数是将实际参数的地址传递给函

19、数的形式参数。总结与注意1)形参必须是变量,用于接收实参传递过来的值。实参可以常量、变量、表达式、函数调用等,无论实参是何种类型的量,在函数调用时都必须有确定的值。实参与形参,数量和类型要能一一对应。2)函数如果在定义前使用,或者在其他文件中。那么必须先声明,再使用。函数运行的时候是根据形参类型去读写形参变量的,而传递过来的实参会根据函数声明里的形参类型进行类型转换。如果声明的函数没有形参列表则传递的实参无法进行类型转换就直接把代表实参的二进制码直接复制给形参。如果实参和形参的数据类型不对,而函数运行时又是根据自己的形参类型去读取形参变量的,所以就会发生读取出错,严重的会使程序停止。声明返回值

20、类型是把数据自动转换成相应的类型才返回给调用者。声明函数的返回值和定义函数时的返回值不一样编译时会提示类型出错。在使用前没有声明或者声明的函数没有声明返回值则编译时默认的返回值都是int型,若该函数定义时声明的返回值不是int型,编译器就也会提示类型错误。3)大多数C语言实现都是通过函数main的返回值来告诉操作系统该函数是否执行成功。一般是返回0表示成功,返回其他值则表示失败。如果main中没有return 0;返回的是一个int型数值,但是不能保证值为0。4.2局部变量和全局变量理解与测试:1)全局变量也叫外部变量,它是定义在函数外部。它不属于那个函数,而属于一个源程序文件。局部变量是定义

21、在函数内部,可以在函数的声明中定义,也可以在一个语句块中定义。变量的作用范围都是相对于同一层次来讲的。例如函数有声明变量,在函数的语句块内又有定义相同名称的变量,则起作用的是语句块内的变量,虽然它们名称相同但是互不影响。如下面的代码:int k = 10;void f(int i) int k = i; k+; printf(%dn,k);main() int k; printf(%dn,k); f(10); for(k=0;k1)分析:像这种有递推公式的就有很明确的递推关系。我们的目的是要求n!,我们只需要两步,第一步是把n!看成n*(n-1)!。第二步求(n-1)!。由于n!和(n-1)!

22、都是求某一个数的阶乘,所以调用函数本身来处理(n-1)!。我们的思路知道此为止,不要在一直想下面怎么处理,程序会按我们这思路自己往下递推的。程序设计:f(int n)一、if(n=1|n=0) n!=1; /设计边界二、n!= n*f(n-1); /利用跟被包含的下一个同概念函数计算 /这里千万不要感觉f()没有写完就不能用,其实递归函数方便的 /就可以随便大胆的调用“自己”。代码如下:double fact(int n);double fact1(int n);main() int n; printf(Input n:); scanf(%d,&n); printf(%d! = %.0fn,n

23、,fact(n); printf(%d! = %.0fn,n,fact1(n); getch();double fact(int n) double f; if(n=1|n=0) /递归函数出口 f = 1; else f = n*fact(n-1); return f;汉诺塔问题:分析:要把A上的n个盘借助C搬到B上,我们的只需要两步。第一步是把n-1个盘搬到C上,再把A上的最后一个盘搬到B。第二步是把C上的n-1个盘借助A搬到B上。由于把n个盘和把n-1个盘的规律相同,所以可以直接调用函数本身。代码如下:void hanio(int n,char a,char b,char c);main

24、() int n; printf(Input the number of disks:); scanf(%d,&n); printf(The steps for %d disks are:n,n); hanio(n,A,B,C); getch();void hanio(int n,char a,char b,char c) if(n=1) /* 递归函数出口 */ printf(%c-%cn,a,b); else hanio(n-1,a,c,b); /* 利用C把n-1个片从A移到B */ printf(%c-%cn,a,b); hanio(n-1,c,b,a); /* 利用B把n-1个片从C

25、移到A */ 九连环解法:分析:我们这边设计是可以解n连环的程序。九连环有个规律是要解或上第n个环必须解去n-1个环。我们解n个环只需要分成两步来处理,第一步就是把解下1个环。第二步就是把剩下n-1个环用解n个环的函数来处理。解一个环的过程是些解下n-2个环,再把第n个环解下,再安上n-2个环(剩下部分要都是未解开的环)。安n个环的步骤过程和解n个环的步骤过程相似。代码如下:#include Stdio.h#include Conio.hstatic int upstep = 0; static int downstep = 0; void UpRing(int n); /*加上函数说明,否则

26、编译将会出一点小错误*/ void DownRing(int n) /*下环的逻辑就体现在这里*/ if(n2) DownRing(n-2); printf(DW:%dt,n);+downstep; if(n2) UpRing(n-2); if(n1) DownRing(n-1); void UpRing(int n) /*上环的逻辑则体现在这里*/ if(n1) UpRing(n-1); if(n2) DownRing(n-2); printf(UP:%dt,n);+upstep; if(n2) UpRing(n-2); main() int n; scanf(“%d”,&n); puts(

27、Starting.); DownRing(n); puts(nEnding.); printf(nup = %dtdown = %dn,upstep,downstep); getch();4)迭代法也称辗转法,是一种不断用变量的旧值递推新值的过程,跟迭代法相对应的是直接法,即一次性解决问题。迭代法又分为精确迭代和近似迭代。“二分法”和“牛顿迭代法”属于近似迭代法。迭代法的思路和递推法相反,迭代是从开始往后面推导。例如计算n!用迭代法如下:double fact1(int n) double f=1; int i; if(n=0) return 1; else for(i=1;i=n;i+) f

28、 = f*i; return f;总结与注意1)用递归法的步骤是:一、把一个事物截去一部分剩下的的部分还是不是该事物如果是就可用递推函数来求解。二、找到截去一部分(剩下的概念上还是该事物)的方法。三、需要把事物截取成多部分函数实现就可以分成两步,首先是用上面的方法把事物截去一部分,最后就是把剩下的部分用这个截去多段事物的函数本身处理。用if设定递归出口截去事物的一部分递归函数F剩下部分用F处理2)递归函数的运行是一个不断递推的过程,每一次递推都要把数据压入栈,如果递推的步骤过多可能就会使栈溢出。所以能用迭代尽量用迭代,一般来说能写出递推式的都可以用迭代。double fact1(int n)

29、double f=1; int i; if(n=0) return 1; else for(i=1;i=n;i+) f = f*i; return f;5指针5.1指针与简单变量理解与测试:简单的讲指针就是地址。指针变量的定义如下: 基本类型 * 指针变量名;指针定义完最好都进行初始化,初始化值可以为NULL。因为没有初始化的指针指向的地址时随机的,如果程序有引用是很容易出错的。总结与注意指针变量指向变量的首地址,指针引用指向的变量时是根据自己的类型来取变量值的,所以不能将不同类型的变量赋值个同一个指针,例如把一个整形指针赋值个符号指针,引用符号型指针指向的变量时只是整形的低字节。5.2指针与

30、数组理解与测试:1)数组名就是一个指向数组首地址的指针常量。对于指针数组符号相当于指针符号*,所以数组元素的地址有下面几种表示:&a0.&aia.a+ipa.pa+i从技术上可以说数组就相当于指针,但是在声明一个数组,C语言编译时会分配一段固定的连续空间,而声明一个指针却不会。2)实际上C语言只有一维数组。多维数组其实可以理解为指针数组和普通数组的结合。例如,char a25;可以理解为一个char *a2数组,里面的每个元素分别指向一个char b5数组。在二维数组x中,数组名x表示数组的首地址,xi表示一个一维数组的名称,(也就是说xi不保存元素值),它只是一个地址。x和xi的值相等,但是

31、*a!=*a0,因为a是一个二维数组首地址指针,a0一个一维数组首地址指针,引用它们指向的变量时会根据自己类型性质定义变量的类型性质。如下代码:void main() char a26=Hello,World; char * p; p = a; if(a=a0) printf(a=a0n); if(*a=*a0) printf(*a=*a0n); if(*p=*a0) printf(*p=*a0n); printf(%dn,sizeof(a); printf(%dn,sizeof(a0); getch();运算结果:a=a0*p=*a0126xi表示数组的首地址,xij表示数组xi的第j+1个

32、元素的值,&xij返回数组xi的第j+1个元素的地址。也可以使用*(xi+j)的方式表示第j+1个元素的值,而*(xi+j)又可表示为*(*(x+i)+j)。3)指针的运算:赋值、间接运算、求地址、递增、递减、求差、比较。指针加上后减去一个整数偏移的长度等于这个整数乘以指针所指数据类型的长度。如下面的代码:int main() unsigned int i; unsigned int *p = &i; printf(%d %d,p,p+1); getch();运行输出:-58 -565.3指针与字符串理解与测试:1)在C系统内部字符串当作一个一维数组,在内存中连续存放。结尾存放字符串结束符,就

33、是ASCII码为0的空字符0,所以字符串常量实质上是指向该字符串首字符的指针常量。如char * p = “point”。数组名就是存放数组的首地址,如 char sa= ”array”;。2)因为字符串名就是指针常量,使用指针数组来存放字符串指针常量可以方便操作多个字符串。由于数组名也是一个指针常量,所以指针数组名是一个指向指针的指针,可以用“*”来读取他被指向的变量。例如下面的代码:void main() char * a2=Hello,World; printf(%sn,a0); printf(%cn,*a); getch();输出的结果是:HelloH3)字符串出来函数主要有输入输出函

34、数:scanf(),printf(),gets(),puts。专门字符串出来函数:strcpy(目的字符串,原字符串),目的字符串是字符串指针,原字符串可以是字符串指针也可以是一个字符串常量。strcat(目的字符串,原字符串),把原字符串中的字符串连接到目的字符串中字符的后面,并删去原字符串后面的字符串结束符。本函数返回的是目的字符串的首地址。strcmp(字符串1,字符串2),按ASCII码顺序比较两个字符串,字符串1等于字符串2返回0,大于返回1,小于返回-1。strlen(字符串),计算字符串长度,不包括结束符。4)输入命令时,在可执行文件(命令)名的后面可以跟一些参数,这些参数被称为

35、命令行参数。主函数mian(int argc,char * argv);有两个参数,第一参数argc接受命令行参数(包括命令名)个数。第二个参数argv是个指针数组,接受以字符串常量形式存放的命令行参数(命令名本身也作为一个参数)。例如下面的代码(文件名:test.exe):int main(int a,char *v) int k,s=0; for(k=1;k=a;k+) s = s+atoi(vk); printf(%d,s); getch();调出命令运行窗口(win7系统可以按windows键+R),输入文件的路径 参数,如下图运行结果:6总结与注意1)使用一维数组和字符串指针都可以处

36、理字符串,但是由于数组名是指针常量,要改变数组sa所代表的字符串只能改变数组内容,而字符串指针可以重新指向其他字符串。例如:char sa=”Hello”;sa = “World”;这是错误的。因为sa只是指针常量,不能赋值。2)使用二维数组和指针数组都可以处理多个字符串,但是由于指针数组保存的只是指向字符串的首地址的指针变量,所以是不能去修改字符串中的字符。如:*a=h;是错误的。二维数组保存的是整个字符串,它可以任意修改字符串的字符。如下代码:void main() char a26=Hello,World; *a = h; *(a+1) = w; printf(%s %sn,a0,a1)

37、; getch();运行输出:hello world5.4指针与函数理解与测试:1)指针可以作为函数的返回值。2)每个函数的定义经过编译后都占用一段内存单元,该函数名就代表函数的入口地址。我们可以定义一个指向函数的指针,通过函数指针来调用函数。函数指针的定义如下:类型名(*变量名)();例如:int (*funptr)();或者类型名(*变量名)(形参类型名列表);例如:int (*funptr)(int,int);用这种方式定义函数指针在引用时输入的形参要和定义的函数指针后面的类型名列表数量和类型相一致。3)函数指针数组的声明、赋值、使用和函数指针变量相似。 声明:类型名(*变量名)()总结与注意1)定义和引用函数指针时不要忘记变量名外面的括号。2)函数指针是一种指针,当引用指向的函数时形参个数是由函数

展开阅读全文
相似文档                                   自信AI助手自信AI助手
猜你喜欢                                   自信AI导航自信AI导航
搜索标签

当前位置:首页 > 通信科技 > 开发语言

移动网页_全站_页脚广告1

关于我们      便捷服务       自信AI       AI导航        获赠5币

©2010-2024 宁波自信网络信息技术有限公司  版权所有

客服电话:4008-655-100  投诉/维权电话:4009-655-100

gongan.png浙公网安备33021202000488号   

icp.png浙ICP备2021020529号-1  |  浙B2-20240490  

关注我们 :gzh.png    weibo.png    LOFTER.png 

客服