C语言简单回顾

说明:已经学过C,假期有空闲时间,想多接触一下算法,先来简单回顾一下C,一些比较熟悉的知识点就不再列举

第一章 为什么要学C语言

①起源
从“Space Travel”到UNIX
19世纪七八十年代,ken和dmr为了免费玩游戏,找到了PDP-7小型机,它具有当时最先进的图形处理能力,由于裸机需要操作系统的支持,他们两个为了共同的目标----快乐地玩耍,共同编写了操作系统UNIX
UNIX最初是用汇编语言写的,汇编语言是一种机器语言,难以被人类理解,于是人们发明介于自然语言和机器语言之间的编程语言,那就是汇编语言。 但是汇编语言不具有“可移植性”,对不同计算机,需要不同的汇编程序,这想玩个游戏可太麻烦了,于是ken和dmr决定改用高级语言编写UNIX,这样就可以在更多类型的计算机上快乐的玩耍了!
除了机器语言和汇编语言,几乎其余的编程语言都可以被称为高级语言,不同高级语言编译器可以把同样的高级语言指令翻译成适应不同机器的指令,因而具有很好的移植性。
经过二人的不懈努力,适合UNIX开发的C语言终于问世,二人也于1983年获得图灵奖。但是,本可以依靠UNIX和C语言收获巨大财富的他们,选择了开源,这种乐于分享,不计回报的精神,才是正宗的“黑客精神”
②学习C语言的必要性
C语言是目前执行效率最高的高级语言

**

第二章 C数据类型

**
①整型常量的八进制、十六进制表示
八进制整数由数字0开头(如021表示十进制17),十六进制整数由数字0加字母x或X开头(如0x11表示十进制17)
②变量名命名
必须以字母或者下划线开头
③赋值表达式
左侧只能是一个变量名 a + b = c 是不合法的
赋值表达式的值为运算结束后左操作数的值
④在计算机内存中,负整数用补码来表示
好处:将减法运算转化为加法运算来处理、可用统一的形式来表示0
⑤double 和 float 的有效位数
double:可接收16位有效数字 flaot:可接收7位有效数字

第三章 简单的算术运算和表达式

①增1运算符和减1运算符
增1运算符和减1运算符的代码执行效率高于一般赋值语句,这两种运算符不能对表达式进行操作
②宏常量和const常量
编译器对宏常量不进行类型检测,对const常量进行类型检测
③运算时的类型转换
不同类型的操作数进行运算时,先转换为类型相同的操作数
a.必然类型转换:shar,short–>int; float–>double
b.有规律的类型转换
④赋值时的类型转换
规则:将右侧表达式的值转换为左侧变量的类型
⑤强制类型转换符
(类型)表达式 eg: (float)5/2 = 2.500000
⑥引入数学函数
#include <math.h>
⑦注意
a.不能对算术表达式进行增1或减1运算
b.求余运算的操作数只能是整型

第四章 键盘输入和屏幕输出

①转义字符
‘\0’: 代表ASCII码值为0的字符,它代表一个字符,不是两个
②“char型是特殊的 int型”
在ASCII码取值范围内,char型数据和int型数据可以进行混合运算
一个char型数据既可以字符型格式输出,也可以整型格式输出
③单个字符的输入输出
a. getchar()
函数getchar()没有参数,函数的返回值就是从键盘终端读入的一个字符,按回车表示输入结束
b.putchar(ch)
把一个字符输入到屏幕当前光标位置
④printf()
a. %f: 以十进制小数形式输出实数(包括单双精度),隐含输出六位小数
b. l----输出long, L—输出long double, h—输出short
c. 指定输出域宽
若m为正整数,当输出数据宽度小于m,数据右对齐,若数据以0开头,左边补0,否则左边补空格;当数据宽度大于m,按实际宽度输出
但对浮点数,若整数部分位数超过了说明的整数位宽度,将按实际整数位输出;
若小数部分位数超过了说明的小数位宽度,则按说明的宽度以四舍五入输出。
如printf("%3.3f", 333.3456); 输出结果为333.346
⑤scanf()
a. %c 输入一个字符,空白字符也作为有效字符读入
b. %s 输入字符串,遇到空白字符,则读入结束(以空白字符开头,则忽略开头)
c. %lf、%le:—double %Lf、%Le:—long double
d. 有域宽修饰符,无精度修饰符
e. *----对应的输入项在读入后不赋给相应的变量 可以实现用户以任意字符作为分隔符输入数据
eg. scanf("%d%*c%d", &a, &b)
f. EOF 宏常量 , 值为-1
输入原理理解:用户通过键盘键入字符串后,按下回车,键入的字符串到达缓冲区,可以通过scanf()或get()从缓冲区读取数据,scanf()读取结束有三种情况:遇到空白符、打到规定域宽、遇到非法字符;get()读取结束就是读到了一个字符,n次读取后,缓冲区还可以有数据,还可以继续读取,fflush(stdin)可以清空缓冲区

第五章 选择控制结构

①关系表达式:由关系运算符将两个操作数连接起来的表达式

**②标准库函数<stdlib.h>:
exit(code)、 rand()、 srand(n)----设置随机数种子n、malloc()等动态分配函数 **

③浮点数比大小
两个浮点数进行比较,应该判断两个浮点数差值的绝对值是否近似为0
④switch(表达式)
括号里的表达式只能为整型或字符型
⑤在两个数之间可加入任意多个空格符的输入操作
scanf("%d %c%d", &data1, &op, &data2); %c前有一个空格

⑥对非法字符输入的检查与处理
说明:scanf()不进行参数类型匹配检查,因此,当参数地址表中的变量类型与格式字符不符时,只是导致数据不能正常读入,但编译器并不提示任何出错信息。
scanf()的返回值是成功读入的数据项数,因此可以检验返回值,如果为成功读入,则清空输入缓冲区fflush(stdin)
⑦位运算符
~:按位取反 <<、>>:左移位右移位 &:与 |:或 ^:异或
左移位:右边空位补0
右移位:如果左边第一位为0,则左边空位补0;如果为1,则左边空位补1

**

第六章 循环控制结构

①while 和 for 循环 都属于当型循环 while 和 do-while是条件控制的循环 for是计数控制的循环
②逗号表达式
表达式1, 表达式2, 表达式n 从左到右计算表达式的值,最终逗号表达式的值为表达式n的值
③延时语句
for(i = 0; i < 100; i++);
④根据时间函数设置随机数种子
<time.h> srand(time(NULL))
函数time()返回从一个标准时间到当前时刻经过的相对时间(单位秒),使用NULL作为参数时,将time(NULL)返回值转化为一个无符号整型,作为随机数种子
⑤清除缓冲区中的所有字符
法一:fflush(stdin) ---------------可能会出现移植性问题
法二:while(getchar() != ‘\n’)
⑥类型溢出提醒
当程序从高位计算机向低位计算机移植时,也可能出现类型溢出问题

第七章 函数

①函数:分而治之与信息隐藏的思想
②关于return 、
函数的返回值类型可以是除数组以外的任何类型(return只能返回一个数据值给调用者)
无须返回任何值的return语句可以写成 return; 这是较好的程序设计风格
③全局变量
全局变量在不指定初值时默认为0
④变量的存储类型
存储类型 数据类型 变量名
auto 自动变量/动态局部变量(默认) 进入语块自动申请内存,退出语块自动释放内存
static 静态变量 在不指定初值时默认为0 生存期是整个程序运行期间
在函数内定义的静态变量称为静态局部变量,只能在函数内访问
在所有函数外定义的静态变量称为静态全局变量,可在定义它的文件内任何地方访问
但是非静态全局变量可以被程序内其他文件访问
静态局部变量在退出函数后仍能保持其值到下一次进入函数

外部变量/全局变量 在所有函数外,没有指定其存储类型的变量
要想被程序中其他文件访问,需要用extern进行声明 即 extern 类型名 变量名;

⑤用const将形参声明为常量可以有效防止形参值在函数内被修改
⑥断言 assert()
<assert.h> assert() 断言仅用于调试程序,不作为程序的功能
断言便于在调试程序时发现错误,告知错误位置
⑦代码风格
a.一行只写一条语句,只定义一个变量 ,定义变量时进行初始化
b.分支、循环体一律用{}
c.函数参数的逗号分隔符和for中分号后面加一个空格
d.表达式较长的if,for语句可适当去掉一些空格
eg. for(i=0, i<10, i++)

第八章 数组

①C99允许用变量定义数组大小,但无法在定义时进行初始化,因为系统暂时不知道要为数组分配多少 内存空间
②按行初始化或按元素初始化时,所给元素个数小于数组规定元素格式,用0补齐
③向函数传递一维数组
a. int Average(int score[], int n);
b. int Average(int* score, int n);
调用函数: aver = Average(score, n);
④折半查找中,防止数值溢出的方法
mid = (high + low ) / 2 改为 mid = low + (high - low) / 2
⑤向函数传递二维数组
void Average(int score[][MAX]) -------这其实是行指针的形式
不能省略第二维的长度声明,因为编译器必须知道一行有多少个元素,才能知道它需要跳过多少个存储单元来确定所以数组元素的位置

第九章 指针

①%p 是输出地址的格式符
②NULL 是在 stdio.h中就定义的宏 可以直接用
③直接寻址与间接寻址
直接寻址:scanf("%d", &a); printf("%d", a);
间接寻址:int *p = &a; *p = 4;
④按值调用与模拟按引用调用
按值调用:传递副本
模拟按引用调用:传递地址值
⑤函数指针
定义及说明:函数指针就是指向函数的指针,指向函数的指针变量中存储的是一个函数在内存中的入口地址,函数也存储在内存中,指向存储这个函数的第一条指令的地址,称为函数的入口地址
函数的入口地址:编译器将不带()的函数名解释为该函数的入口地址(这一点和数组很像)
具体使用:
void Sort(int a[], int n, int (*compare)(int a, int b));
int (*compare)(int a, int b) compare是一个函数指针,该函数指针指向一个两个整型形参,返回值为整型的函数,(*compare)括号必不可少
在Sort函数内部,若想引用函数指针指向的函数有两种方式(和数组一样)
第一种:指针解引用 eg: if((*compare)(a[j], a[k]))
第二种:把函数指针当成函数名(类比把数组指针当成数组名) eg:if(compare(a[j], a[k]))

第十章 字符串

①编译器对字符串的默认处理
为便于确定字符串的长度,C编译器会自动在字符串末尾添加一个ASCII码值为0的空操作符‘\0’作为字符串结束的标志,在字符串中可以不显示地写出
②字符串的存储
采用字符型数组来存储字符串,但字符型数组不一定是字符串,要看最后一个元素是否为’\0’,’\0’不计入字符串长度,计入数组长度
③字符串常量就代表字符串首地址(字符串常量和数组名,函数名类似)
所以有 char *ptr; ptr = “hello world”; ptr就是字符串的首地址
但由于字符串常量存储在只读的常量存储区,它的内容不能被修改 ,要方便地修改字符串内容,需要用数组存储字符串,但是注意数组名又是一个只读的地址常量,其值不能被修改,所以没有数组名++
④字符串的输入
方法一:scanf("%s", str);
表示读入一个字符串,直到遇到空白字符为止
方法二:gets(str)
以回车符作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分,而scanf()不读走回车符,回车符仍留在缓冲区中,但scanf()会读走截止时之后的的第一批所有空白符
方法三:能限制字符串输入长度的函数fgets(name, sizeof(name), stdin)
。。。 name是定义好大小的字符型数组

⑤字符串的输出
方法一:printf("%s", str);
方法二:puts(str);
不同的是,puts()在读取完字符串后会自动输出一个换行符
⑥字符串处理函数<string.h>
strlen(str); strncpy(str1, str2, n) ; strncmp(str1, str2, n); strncat(str1, str2, n);
⑦为防止实参在被调函数中被意外修改,可以在相应形参前面加上类型限定符const
⑧const类型限定符的四种应用
a. const int p = &a 与 int const p = &a
都表示
p是一个常量,不能修改,p是变量,可以修改
b. int
const p = &a
表示p是一个常量,不能修改,但是p的指向p是变量,可以修改
c. const int
const p = &a
p 和p 都是常量
⑨字符处理函数<ctype.h>
int isdigut( int c) 如果C是数字,则返回真
……
⑩数值字符串向数值的转换<stdlib.h>
int atoi(const char
str) 将str指向的数值字符串转换为整型数,转换时忽略数值字符串前的换行符

第十一章 指针和数组

①p + 1 :表示指针变量p的值加上1*sizeof(基类型)个字节
②二维数组的行列指针
行指针: 声明-----int (*p)[4] 初始化---- p = a; / p = &a[0]
所以p[i][j] = (p[i] + j) = ((p + i) + j)
核心:a[i][j]可以理解为:a[i]是一维数组的数组名,代表了第i个元素的地址
所以有a[i][j] = (a[i] + j) = ((a + i) + j) = ((a + i))[j]
列指针: int *p ; p = a[0] / p = a / p = &a[0][0]
核心:将数组a看成由m
n个元素组成的一维数组
a、&a[0]-----------代表第0行的地址
a、a[0]、a[0][0]------------------代表第0行第0列的地址
③向函数传递二维数组
方法一:行指针
a.
void Input(int p[][[N], int m, int n);
int a[3][4];
Input(a, 3, 4);
在函数内部引用数组值:a[i][j]
在函数内部引用数组地址:&a[i][j]

b.
void Input(int (p)[[N], int m, int n);
int a[3][4];
Input(a, 3, 4);
**在函数内部引用数组值:
((p + i) + j)
在函数内部引用数组地址:
(p + i) + j**

方法二: 列指针
void Input(int *p, int m, int n);
int a[3][4];
Input(&a[0][0], 3, 4); //////Input(*a, 3, 4);
在函数内部引用数组值:p[ i×n+j ]
在函数内部引用数组地址:&p[ i×n+j] //这里用×代替C语言中的乘号

总结:
使用行指针——按最熟悉的方法不加考虑地传递,劣势就是改变数组列数时要修改较多地方
使用列指针时,函数中定义形参直接用指针,调用函数时,传入&a[0][0]——第0行第0列地址

④指针数组
定义:由若干个基类相同的指针构成的数组称为指针数组
主要用途:对多个字符串进行处理操作
char pStr[N];
char name[N][MAX_LEN]
pStr[i] = name[i]-----------------让pStr[i]指向二位字符数组name的第i行
⑤物理排序与索引排序
物理排序:通过移动字符串在实际物理存储空间中的存放位置而实现的排序
索引排序:通过移动字符串的索引地址实现的排序
⑥命令行参数
在C程序中,命令行参数是通过main()函数来传递给程序的
之前对这个完全不了解,学的时候就不懂,现在看了一点Python和linux,感觉和linux的终端以及windows的cmd有联系,查了一下还真是,但是今天的重点不在这里,就一笔带过了
一般形式为int main(int argc, char argv[]) argc表示命令行中参数个数 argv[]用于存放各个命令行参数首地址
⑦C程序的内存映像
在这里插入图片描述
如图
只读存储区:用于存储程序的机器代码和字符串常量等
静态存储区:静态变量、全局变量
堆:一个自由存储区,可用C的动态内存分配函数俩使用它
栈:保存函数调用时的返回地址、函数的形参、局部变量以及CPU的当前状态等程序运行信息
⑧动态内存分配函数<stdlib.h>
a. void
malloc(unsigned int size);
通用指针指针:void * 也叫无类型指针,来定义基类型未知的指针,因此若想将函数返回值赋给某个指针,应该先强转为某种基类型指针 如 int pi = (int )malloc(10 * sizeof(int)) ;
b. void
calloc(unsigned int num, unsigned int size);
指定分配几个空间以及每个空间的大小,并自动将分配的内存初始化为0
c. void
realloc(void* p, unsigned int size);
改变原来分配的存储空间的大小,*p是指向原来分配空间的指针,当改变动态分配空间大小时,原数据会丢失,所以要慎重
当程序结束时,记得free§
⑨长度可变的二维动态数组
用的是列指针
⑩注意:
要检查内存是否分配成功
使用指针要先赋初值
数组访问不要越界
记得释放内存
记得释放内存后,将相对应的指针变量赋值为NULL
在函数中,动态分配的内存,在函数调用结束后并不会被释放!! 必须使用free()才会被释放,所以可以在函数中向堆申请空间,给这些空间赋值,函数调用结束后,这些值不会变成乱码
在学数据结构的时候,这一点一直有点模糊,现在才恍然大悟……唉,基础不牢,地动山摇!
对内存映像不清楚,堆和栈的特点不熟悉,这一点要经常温习
在这里插入图片描述

第十二章 结构体与共用体

①结构体模板只是声明了一种数据类型,定义了数据的组织形式,并未声明结构体类型的变量,因而编译器不为其分配内存
②并非所有的结构体成员都可以使用赋值运算符来赋值,对于字符数组烈性的结构体成员,赋值时必须使用strcpy()
③结构体所占内存的字节数与结构体定义和操作系统均相关,有的操作系统采用内存对齐,以方便读取结构体成员,有的不采用,还是sizeof(结构体)靠谱
④结构体数组与结构体指针
结构体指针访问结构体成员的方式:
方法一:成员选择运算符(*p.成员名)
方法二(常用):箭头运算符 p->成员名
⑤结构体可以作为函数返回值的类型
⑥共用体
定义:共用体,也成联合,是将不同类型的数据组织在一起共同占用同一段内存的一种构造数据类型
声明: typedef union sample{
short i;
char ch;
float f;
} SAMPLE
内存:4 取决于占内存空间最多的成员
理解:共用体是个单选题,每个时刻起作用的只有一个成员,因此不能对所有成员同时初始化,只能对第一个成员初始化,共用体不能进行比较操作,不能作为函数参数
⑦枚举类型
说明:当某些量仅由有限个数据值组成时,通常用枚举类型来表示
举例:enum response {no, yes, none}; //声明了一个名为response的枚举类型
enum response answer; ///实例化了一个枚举类型变量answer
花括号里的标识符都是枚举常量,默认0,1,2……
answer 就是一个整型变量,特殊之处在于它只能取no,yes,none这些值,比如可以赋值answer = no;
⑧链表
学完数据结构,这玩意儿我可太熟了,就不说了

第十三章 文件操作

①文本文件与二进制文件
区别:存储数值型数据的方式不同
文本文件中,每一位数字以ASCII码形式存储,二进制文件中把整个数字作为一个二进制数来存储
优缺点比较
文本文件便于被其他程序读取,便于对字符进行逐个处理,便于输出字符,但占地儿,二进制文件反过来就行了
②缓冲型与非缓冲型文件系统
缓冲型文件系统,在内存中为程序和文件之间设立了一个缓冲区,缓冲型文件系统利用文件指针标识文件; 非缓冲型文件系统无缓冲区,利用文件号整数标识文件,我们介绍缓冲型文件系统中的文件操作,也称高级文件操作
③FILE* fopen(const char *filename, const char *mode);
FILE是定义在stdio中的一个结构体类型,封装了文件有关信息,如文件位置指针,缓冲区等,filename可包含路径+文件名,mode表示文件打开方式
如果没打开,返回NULL
mode打开方式
“r”, “w”, “a”, “+”, “b”
“r”只读
"a"只写,在已有(必须是已存在)文件后继续写入
“w”只写,不管有没有,重新创建或者覆盖名为name的文件,并写入内容
“+”结合以上几种,实现读写文件
“b”结合以上几种,对二进制文件进行操作
②int fclose(FILE *fp)
关闭成功返回0,否则返回非0
③按字符读写文件
a. int fgetc(FILE *fp);
功能:从fp所指文件中读取一个字符,并将位置指针指向下一个字符,若读取成功,则返回该字符,若读到文件末尾,返回EOF
使用getchar()输入字符时,先将所有字符送到缓冲区再写入文件
b. int fputc(int c, FILE *fp);
功能:将字符c写到文件指针fp所指向的文件中,若写入错误返回EOF,否则返回字符c
c.读取文件中的字符串
char *fgets(char *s, int n, FILE *fp);
功能:该函数从fp所指的文件中读取字符串,并在字符串末尾添加’\0’,然后存入s,最多读取n-1个字符,当读到回车、到达文件末尾或读满n-1个字符时,函数返回字符串的首地址,即指针s的值。读取失败,返回NULL
d. int fputs(const char *s, FILE *fp );
若写入错误,返回EOF,否则返回非负数

fgets()与gets()的比较:fgets()可以读取换行符,gets()不行
fputs()与puts()的比较:fputs()不会在写入文件的字符串末尾加换行符
④按格式读写文件
a. int fscanf(FILE *fp, const char *format, …);
第一个参数为文件指针,第二个参数为格式控制参数,第三参数为地址参数表,后两个参数和返回值与scanf()一样.
我现在才知道,原来scanf()里第一个双引号里的内容是一个字符串指针!!!!
b. int fprintf(FILE *fp, const char *format, …);
评价:用函数fscanf()和函数fprintf()进行文件的格式化读写,更方便,容易理解,但输入时要将ASCII字符转换成二进制数,输出时要将二进制数转换成ASCII字符,耗时较多
⑤按数据块读写文件
a. unsigned int fread(void *buffer, unsigned int size, unsigned int count, FILE *fp);
功能:从fp所指的文件中读取数据块,并存储到buffer指向的内存中,buffer是每个待读入数据块的起始地址,size是每个数据块大小,count是最多允许读取数据块个数,函数返回的是实际写入的数据快个数
eg: for(i=0; !foef(fp); ++)
{
fread(&stu[i], sizeof(STUDENT), 1,fp);
}
b. unsigned int fwrite(void *buffer, unsigned int size, unsigned int count, FILE *fp);
功能可类比得出
eg: fwrite(stu, sizeof(STUDENT), n, fp);
⑥文件的随机读写-------文件位置指针
a. int fseek(FILE *fp, long offset, int fromwhere);
功能:将fp的文件位置指针从fromwhere开始移动offset个字节,指示下一个要读取的数据的位置
fromwhere的三个取值:SEEK_SET 或0:文件开始处 ;;; SEEK_CUR或 1:文件当前位置
SEEK_END 或2:文件结尾处
函数调用成功则返回0,否则非0
b. void rewind(FILE *fp);
重置位置指针到文件首部
c. long ftell(FILE *fp);
返回文件指针当前位置
d. int fflush(FILE *fp);
无条件地把缓冲区中所有数据写入fp指向的文件中
⑦补充知识:
很多dos命令在windows下的命令提示符(cmd)下是照样可以使用的,但DOS和CMD是不同的,你在windows操作系统里进的DOS(即输入 CMD 进命令提示符)不是纯DOS,只是为方便某些需求而建立的,而纯DOS本身就是一种操作系统.(两者的区别:比如你可以在纯DOS下删除你的 windows系统,但在你所说的"命令提示符"里却不能,因为你不可能"在房子里面拆房子吧?")

dos是磁盘操作系统;命令提示符是dos系统的界面中输入dos命令的提示位置;cmd是xp系统运行其自带dos的命令。
⑧标准输入/输出重定向
对于终端设备,系统会自动打开三个标准文件:标准输入,标准输出,标准错误输出。
相应地,系统定义了3个文件指针常数:stdin,stdout,stderr,分别指向以三个标准文件,这三个文件都以标准终端作为输入输出对象
在默认情况下,标准输入时键盘,标准输出是屏幕。

fprintf()只是 prinft()的文件操作版,二者的差别在于fprintf()多了一个FILE*类型的参数fp,如果为其提供的第一个参数是stdout,那它就和printf()一样了
putchar©和 fput(c,stdout),,, getchar©和 fgetc(stdin)等价

虽然系统隐含的标准I/O文件是指终端设备,但标准输入和输出是可以重定向的,操作系统可以重定向他们到其他文件或文件属性的设备,只有标准错误输出一般不行
<:输入重定向 >:输出重定向
举例:exefile 是可执行文件的文件名
从文件file.in读入数据而不是从键盘 在DOS命令提示符中键入
C:\exefile < file.in
将数据输出到dile.out而不是屏幕 在DOS命令提示符下键入
C:\exefile > file.out

总结:第一次学习这门课程时,之前完全没有基础,加上课讲得很快,学起来感觉很吃力,有时候甚至有不少负面的消极的情绪,觉得知识点那么多,还看不懂,非常苦恼,考完试之后,本打算利用寒假好好再看一遍,可惜自制力不强,也没看,基础不牢,就直接学了数据结构,留下了遗憾,这个暑假稍微清闲些,必须要把遗憾补上。这次复习,收获很多,因为学的东西越来越多,很多以前不懂得地方,突然全都通了,几天复习下来,感觉神清气爽,结构很清晰,这本书的知识点也完全没有我当时觉得的那么多,那么可怕。
其实对于我们不熟悉的事物,我们难免会产生畏惧感,惰性,不想不敢去面对,其实没什么大不了,没什么语言不是熟能生巧的,以积极乐观的心情面对它,用的多了,敲得多了,自然就掌握了。
话又说回来,语言的掌握只是一切的起点,是我们必须要做到的,前路有更多有趣的东西在等待我们的发掘!
一起加油吧,少年!

**

猜你喜欢

转载自blog.csdn.net/Charles___6/article/details/107742093