【C语言学习笔记】翁恺课程(1)

目录

0. 前言

1. 第一个程序

2. 变量

变量赋值与初始化

常量

浮点数

表达式

3. 判断机制

if条件判断

嵌套的if-else

级联的if-else

多路分支:switch-case语句

4. 循环

while循环

 do-while循环

算平均数

整数求逆

for循环

循环次数

循环控制

 循环嵌套

凑硬币

求前N项和

整数分解

最大公约数


0. 前言

计算机程序的执行:
1) 解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行
2) 编译:借助一个程序把你的程序翻译成机器语言写的程序,计算机可以直接执行。
解释语言vs编译语言:
语言本无解释与编译之分,常用的执行方式不同。
解释型语言有特殊的计算能力;编译型语言有确定的运算性能。

C语言的用途:操作系统、嵌入式系统、驱动程序、底层驱动、图形引擎、图像处理、声音效果(直接给我们提供数据的代码不是用C语言写的)一些底层的东西。

C语言是一种工业语言,基础性工作使用。
C语言需要被编译才能运行,所以需要:编辑器和编译器。(或者IDE,Integrated Development Environment集成开发环境)

1. 第一个程序

# include int main()
{printf("hello world!\n");return 0;
}

printf()会把" "内的内容(字符串)原封不动地输出,\n表示换行。
除了printf这行 其他为框架,为必须部分。

# include int main()
{printf("hello world! \n");printf("%d \n",12+34);printf("12+34=%d \n",12+34);return 0;
}

printf("%d",12+34);    %d则是输出后面的值,12+34为式子,则算后输出。
四则运算:加+ 减- 乘* 除/  取余%(取两个数相除后的余数(27除以6, 商数为4,余数为3))  括号()

2. 变量

找零钱问题
1) 有办法输入数字;
2) 有地方放输入的数字;
3) 输入的数字参与计算。

# include int main()
{int price = 0;//定义了整形变量price,类型是int,初始值=0printf("请输入金额(元):");scanf("%d",&price);//要求scanf函数读入下一个整数,结果赋值给变量priceint change = 100 - price;//定义了整形变量change,并做了计算printf("找您%d元。\n",change);return 0;
}

如何输入:在终端窗口中;以行为单位进行,行的结束标志为按下回车键,按下回车前程序不会读到任何东西。

变量:

int price = 0:定义了整形变量price,名字为price,类型是int,初始值为0。
变量是一个保存数据的地方。
变量定义的一般形式就是:<类型名称> <变量名称>
变量的名字叫“标识符”

基本原则:
- 只能由字母、数字、下划线构成,
- 数字不能出现在第一个位置上。
- C语言的关键字(又叫保留字)不可用作字符。

变量赋值与初始化

price = 0:式子,= 赋值运算符,将右边的值赋给左边。
和数学不同,a=b 与 b=a意思完全相反。

当赋值发生在定义变量时,为变量的初始化。
虽然C语言并没有强制要求所有的变量在被定义的地方做初始化,但所有变量在第一次被使用(出现在赋值运算符等号右边)前都应该被赋值一次。
如果没有被初始化?
则变量的值就是其在内存中的原来的值。
变量初始化:<类型名称> <变量名称>=<初始值>
也可以在组合变量定义中给单个变量单独赋初值,如

int price=0, change=10;

有运算符的式子就叫表达式(比如=是赋值运算符),如

price = 0;
charge=100-price;

C语言是有类型的语言,所有变量在使用前必须先定义或声明;
所有变量必须有确定的数据类型(表示在变量中可以存放什么样的数据),变量中也只能存放指定类型的数据,程序运行过程中也不能改变变量的类型。
传统的ANSI C只能在开头的地方定义变量,而C99可以任何地方定义。


读整数:

scanf("%d",&price);//要求scanf函数读入下一个整数,结果赋值给变量price;注意有 & 
如果输入非整数,后面再说。
出现在scanf字符串里面的东西是它一定要你输入的东西,而不是给你看的东西。

scanf("%d,%d",&a,&b);

键盘输入:1,2回车

键盘输入:1空格2回车

scanf("price%d %d",&a,&b);

键盘输入:1空格2回车

键盘输入:Price1空格3回车

常量

const int AMOUNT=100;

其中const是个修饰符,加在int前面,给这个变量加上一个const(不变的)属性,表示这个变量的值一旦初始化,就不能再更改赋值了。
好处:1.便于理解、2.便于修改
一般常量名字全大写
 输入时如果要两个数,在中间、最后敲空格或回车,计算机读到两个数字时才会停止。

浮点数

两个整数做运算结果只能是整数,去掉小数部分,10 和10.0 在C中为完全不同的数

浮点数(浮点数指小数点时可以浮动的,是计算机中表示分数和无理数的一种方式。人们用浮点数来称呼有小数点的数),C中无定点数。

当浮点数和整数放在一起运算时,计算机会自动把整数转化为浮点数计算。

# include 
/* 身高单位英尺英寸转为米单位  */
int main()
{printf("请分别输入身高的英尺和英寸,""如输入\"5 7\"表示5英寸7英尺:" );int foot, inch;scanf("%d %d",&foot,&inch);printf("身高是%f米。\n",((foot + inch / 12)*0.3048));printf("10/3*3 = %d\n",10/3*3);    // 输出结果为9,即10/3=3.333去掉小数部分,为3 3*3=9printf("10.0/3*3 = %f\n",10.0/3*3);// 10 改进为10.0,%d改成%f。printf("身高是%f米。\n",((foot + inch / 12.0)*0.3048));// 12 改进为12.0,%d改成%f。return 0;
}

浮点数类型,可以为double 双精度浮点数,float (单精度)浮点数。,若定义变量时就改int为double,则 scanf、printf 的 %d 部分也应改为 %lf %f

表达式

运算符(operator)是指进行运算的动作,比如加/减法运算符+ -
算子(operand)是指参与运算的,可能是常数/变量/一个方法的返回值。

a = b + 5:= + 为运算符; a b 5为算子

# include 
/*   求时间差   */int main()
{printf("请分别两次时刻的小时和分钟,如4小时30分:4 30\n");int hour1, minute1;int hour2,minute2;scanf("%d %d",&hour1,&minute1);scanf("%d %d",&hour2,&minute2);int t1 = hour1*60 + minute1;	// 转换为分钟为单位,防止进位问题 int t2 = hour2*60 + minute2;int t = t2 - t1;printf("时间差是 %d 小时 %d 分。\n", t/60, t%60); // t%60为求分钟部分 return 0;
}

运算符的优先级

优先级运算符运算结合关系举例
1+单目不变自右向左a*+b
1-单目取负自右向左a*-b
2*自左向右a*b
2/自左向右a/b
2%取余自左向右a%b
3+自左向右a+b
3-自左向右a-b
4=赋值自右向左a=b

(单目:如+a,-a,相当于正负号;双目:a+b,a-b);赋值也是运算,也有结果。
a=6的结果就是a被赋予6;a=b=6∶自右向左,先b =6 后再 a =b。

# include 
/*   交换a b的值  */int main()
{int a = 5,b = 6;int t;t = a;a = b;b = t;printf("a = %d,b = %d\n",a,b);
}

(8条消息) C语言:使用Dev C++断点调试_qq_45660901的博客-CSDN博客_c语言设置断点调试步骤https://blog.csdn.net/qq_45660901/article/details/109129667?spm=a2c6h.12873639.article-detail.8.69b2518aHcX6uF复合运算符:+ - * / % 和 = 结合形成+= -= *= /= %=

total += 5 即是 total = total + 5;

total += (sum+100)/2 即是 total = total +(sum+100)/2;

total *= sum+12 即是 total = total*(sum+12);右边先算完,再做复合运算符。

递增递减运算符:++  -- 。是单目运算符,只能有一个算子,这个算子还只能是变量。会让该变量+1/-1;

a++后缀形式:a++ 运算式的值为a+1 的值。

++a前缀形式:++a 运算式的值加了1之后的值。

无论哪个 a自己的值都是加了1的

表达式运算表达式的值
count++给count加1count原来的值
++count给count加1count+1后的值
count--给count减1count原来的值
--count给count减1count-1后的值

a = b += c++-d+--e/-f 表示啥?
 

3. 判断机制

if条件判断

    if(条件){要执行的语句,只有条件成立时才会执行}
# include 
/* 求时间差,if结构判断是否有借位 */
int main()
{int hour1, minute1;int hour2, minute2;scanf("%d %d",&hour1,&minute1);scanf("%d %d",&hour2,&minute2);int ih = hour2 - hour1;int im = minute2 - minute1;if ( im < 0){im = 60 + im;ih --;}printf("时间差为%d小时%d分。\n", ih,im);return 0;
}

 条件

计算两个值之间的关系,关系运算:== 相等 ; != 不相等;> 大于;>= 大于等于;< 小于;<= 小于等于。

关系运算的结果:成立 为1;不成立 为0。·

优先级:比算术运算低 比赋值运算高。== 与!=优先级比其他的低,连续的关系运算从左到右。

5>3==6>4表示:5大于3是否等于6大于4 

6>5>4 表示:6大于5是否成立 成立为1 1 > 4是否成立,不成立 结果为0。

找零计算器

# include 
/*   找零计算器  */int main()
{//初始化int price=0;int bill=0;//读入金额和票面printf("请输入金额:");scanf("%d",&price);printf("请输入票面:");scanf("%d",&bill);//计算找零if (bill >= price){printf("应找您:%d\n",bill - price);}else{printf("您的钱不够\n");}return 0;
}

//是(单行)注释;  /* */中间可以有多行注释。
comment对程序的功能没有任何影响,但是往往能使程序更容易被人理解。

输入比较两数求大者:

# include 
/*   比较ab大小_方案1  */int main()
{int a,b;printf("请输入两个整数:");scanf("%d %d",&a,&b);int max=0;if(a>b){max=a;}else {max=b;}printf("大的那个是%d\n",max);return 0;
}
# include 
/*   比较ab大小_方案2  */int main()
{int a,b;printf("请输入两个整数:");scanf("%d %d",&a,&b);int max=b;if(a>b)max=a;printf("大的那个是%d\n",max);return 0;
}

if语句可以没有大括号{},if语句这一行结束的时候并没有表示语句结束的“;”,而后面的赋值语句写在if的下一行,并且缩进了,在这一行结束的时候有一个表示语句结束的;这表明这条赋值语句是if语句的一部分,if拥有和控制这条赋值语句,觉得是否执行。else也是,可以没大括号,只有紧跟的那一句有效。

# include 
/*   计算薪水  */int main()
{//初始化const double RATE = 8.25; //标准工资const int STANDARD = 40;double pay = 0.0;int hours;printf("请输入工作小时数:");scanf("%d",&hours);printf("\n");if (hours >= STANDARD){pay = STANDARD * RATE + (hours-STANDARD)*(RATE*1.5); // 1.5倍加班工资}else{pay = hours * RATE;}printf("应付工资:%f\n", pay);return 0;
}

嵌套的if-else

找abc的最大值:

# include 
/*   比较abc最大值  */int main()
{//初始化int a,b,c;scanf("%d %d %d",&a,&b,&c);int max = 0;if(a>b){if(a>c){max = a;}else{max = c;}}else{if(b>c){max = b;}else{max = c;}}printf("max = %d\n",max);return 0;
}
if(a>b)
{if(a>c)max=a;else max=c;
}
else
{if(b>c)max=b;else max=c;
}

当if的条件满足或不满足的时候要执行的语句也是一条if或if-else语句,为嵌套的if语句。

如果省略掉大括号,容易引起自己认为、别人认为、编译器认为有区别。最好都写上{}。

else与最近的if联系

级联的if-else

# include 
/*   分段函数  */int main()
{//初始化int x;scanf("%d",&x);int f = 0;if(x < 0){f = -1;}else if(x == 0){f = 0;}else if (x > 5){f = 2*x;}else{f = 3*x;}printf("f = %d\n",f);return 0;
}

另一种方法:在上面代码上改为每次决定了就输出值。

第一种方法好,有单一出口(都是输出f),代码要降低重复性,出问题的时候好统一处理。

多路分支:switch-case语句

# include 
/*   Swith-case语句  */int main()
{int type;scanf("%d", &type);switch (type) {case 1:printf("Good Morning!");break;case 2:printf("Good afternoon!");break;default:printf("Good Night!");break;}return 0;
}

if-else从上向下判断,上面的都得执行了不满足条件才到下面。

switch-case则是从case中找到对应的值,执行后面的语句,直到遇到break跳出switch,或switch结束为止(即使后面没break,有了下一个case,就执行下一个case里的语句。)

switch后括号里的控制表达式之只能是整数型int的结果。case后面的常数,也可以是常数计算表达式。

4. 循环

写程序写的是步骤,不是关系,不是说明。

while循环

if换成while,if是一次性的,做了之后满足不满足,都离开;while反复试,直到不满足跳出循环体。在循环体内要有改变条件的机会。

#include 
/*  数正整数数字的位数*/
int main()
{int x;int n=0;scanf ("%d" ,&x);n++;x/=10;while (x>0) {n++;x /= 10;}printf("%d\n",n);return 0;
}

do-while

数字位数的算法

  1. 用户输入x;
  2. 初始化n为0;
  3. X/=10,去掉个位;
  4. n++;
  5. 如果x>0,回到3;
  6. 否则n就是结果。

 do-while循环

进入循环的时候不做检查,而是执行完一轮循环体的代码之后再来检查循环条件是否满足,满足的话继续,不满足的话结束循环。无论如何,循环都会执行至少一遍。

do{<循环结构体>}while(<循环条件>);
#include 
/*  数正整数数字的位数*/
int main()
{int x;int n=0;scanf ("%d" ,&x);n++;x/=10;do{n++;x /= 10;}while (x>0);printf("%d\n",n);return 0;
}

猜数游戏

- 让计算机来想一个数,然后让用户来猜,用户每输入一个数,就告诉它是大了还是小了,直到用户猜中为正,最后还要告诉用户它猜了多少次。
- 因为需要不断重复让用户猜,所以需要用到循环
- 在实际写出程序之前,我们可以先用文字描述程序的思路
- 核心重点是循环的条件
- 人们往往会考虑循环终止的条件

l.计算机随机想一个数,记在变量number里;2.一个负责计次数的变量count初始化为0;3.让用户输入一个数字a;
4. count递增(加一);
5.判断a和number的大小关系,如果a大,就输出“大”;如果a小就输出“小”;
6.如果a和number是不相等的(无论大还是小),程序转回到第3步;
7.否则,程序输出“猜中”和次数,然后结束。
 

#include 
#include 
#include /*  猜数游戏  */int main()
{srand(time(0));int number = rand()%100+1;//想要a为100以内的整数:用取余即可(a%=100)int count = 0;int a = 0;printf("我已经想好了一个1到100之间的数。");do {printf("请猜这个1到100之间数:");scanf("%d",&a);count ++;if ( a > number ) {printf("你猜的数大了。");}else if ( a < number ) {printf("你猜的数小了。");}}while (a != number);printf("太好了,你用了%d次就猜到了答案。\n",count);return 0;
}

算平均数

让用户输入一系列的正整数,最后输入-1表示输入结束,然后程序计算出这些数字的平均数,输出输入的数字的个数和平均数。

变量->算法->流程图->程序

变量

—— 一个记录读到的整数的变量平均数要怎么算?
—— 只需要每读到一个数,就把它加到一个累加的变量里,到全部数据读完,再拿它去除读到的数的个数就可以了
—— 一个变量记录累加的结果,一个变量记录读到的数的个数。

#include 
/*  算平均数  */// do while方法,多了一次if判断,且在循环里面,每次都得运行int main( )
{int number;int sum = 0;int count = 0;do {scanf( "%d", &number);if ( number != -1 ) {sum += number;count ++;}}while ( number != -1 );printf("%f\n", 1.0*sum/ count);return 0;
}//while方法,后面循环中比较只进行一次,scanf多了一次,但是是整个程序运行一次。
int main( )
{int number;int sum = 0;int count = 0;scanf( "%d",&number);while ( number != -1 ) {sum += number;count ++;scanf ( "%d",&number); //}printf("%f\n", 1.0*sum/ count);//为能输出小数return 0;
}

整数求逆

● —个整数是由1至多位数字组成的,如何分解出整数的各个位上的数字,然后加以计算?
对一个整数做 %10 的操作,就得到它的个位数
对一个整数做 /10 的操作,就去掉了它的个位数
● 然后再对2的结果做%10,就得到原来数的十位数了;
● 依此类推。

#include 
/*  整数求逆序  */int main( )
{int x = 12345;int digit;int ret = 0;while (x>0) {digit = x%10;//printf("%d", digit);ret = ret*10 + digit;printf("x=%d, digit=%d,ret=%d\n", x, digit, ret);x /= 10;}printf("逆序结果为:%d\n",ret);return 0;
}// 对于700这种输出需要为007的int main( )
{int x = 700;int digit;int ret = 0;while (x>0) {digit = x%10;printf("%d", digit);ret = ret*10 + digit;//printf("x=%d, digit=%d,ret=%d\n", x, digit, ret);x /= 10;}//printf("逆序结果为:%d\n",ret);return 0;
}

for循环

求阶乘:n! = 1x2x3x4x...xn;
变量:显然读用户的输入需要一个int的n,然后计算的结果需要用一个变量保存,可以是int的factor,在计算中需要有一个变量不断地从1递增到n,那可以是int的i。

for循环像一个计数循环:设定一个计数器,初始化它,然后在计数器到达某值之前,重复执行循环体,而每执行一轮循环,讦数器值以一定步进进行调整,比如加1或者减1。
for = 对于
for ( count=10; count>0; count-- )
就读成:“对于一开始的count=10,当count>0时,重复做循环体,每一轮循环在做完循环体内语句后,使得count--”
求和初始化变量=0 求积初始化变量=1。

循环控制变量i只在循环里被使用了,循环外面它没有任何用处。
因此,我们可以把变量i的定义写到for语句里面去:for(int i=0; i

#include 
/*  求阶乘  */int main( )
{int n;scanf ( "%d" , &n) ;int fact = 1;int i = 1;while ( i <= n ) {fact *= i;i++;}printf( "%d !=%d\n", n, fact) ;return 0;
}// for循环做
int main( )
{int n;scanf ( "%d" , &n) ;int fact = 1;//int i = 1;//for ( i=1; i<=n; i++ ) {//	fact *= i;//}//从n乘到1:for(int n1=n;n1>1;n1--){fact *= n1;}printf ( "%d !=%d\n", n, fact) ;return 0;
}

循环次数

for ( i=0; i 则循环的次数是n,而循环结束以后,i的值是n。
循环的控制变量i,是选择从0开始还是从1开始,是判断i

#include int main( )
{int i;for ( i=0; i<5; i++ ){printf("i=%d ", i);}printf("\n最后i=%d\n", i);//循环次数与循环变量最终值int j;for ( j=1; j<=5; j++ ){printf("i=%d ", j);}printf("\n最后j=%d\n", j);//for循环改写为while循环int m = 1;while(m<=5){printf("m=%d ", m);m++;}printf("\n最后m=%d\n", j);return 0;
}

i=0 i=1 i=2 i=3 i=4最后i=5.
for ( 初始动作;条件;每轮的动作){}
for中的每一个表达式都是可以省略的for (;条件;) == while (条件)。

—— 如果有固定次数,用for
—— 如果必须执行一次,用do_while
—— 其他情况用while

循环控制

素数,只能被1和自己整除的数,不包括1;2,3,5,7,11,14,13,17···。

#include 
//判断是否是素数int main( )
{int x;scanf("%d",&x);int i;int isPrime = 1;for(i=2; i

如果没有break,当输入数为6时,第一次判断就知道了不是素数,但仍然满足循环条件,会一直循环到不满足条件才跳出。遇到break,可以跳出循环。
continue:跳过循环这一轮剩下的语句,进入下一轮。

 循环嵌套

注意循环控制变量不能混淆。

#include 
//输出一百以内的素数int main( )
{for(int x=2; x<100; x++){int isPrime = 1;int i;for(i=2; i

凑硬币

#include 
// 凑硬币 用1角、2角、5角的硬币凑出2元int main( )
{int x;int one,two,five;x = 2;				// 凑出2元for(one = 1; one < x*10; one++){for(two = 1; two < x*10/2; two++){for(five = 1; five < x*10/5; five++){if(one + two*2 + five*5 == x*10){printf("可以用%d个1角加%d个2角加%d个5角凑出得到%d元\n",one,two,five,x);}}}}return 0;
}// 期望找到一组解后就跳出输出出来。方法1,break。
int main( )
{int x;int one,two,five;int exit = 0;x = 2;				// 凑出2元for(one = 1; one < x*10; one++){for(two = 1; two < x*10/2; two++){for(five = 1; five < x*10/5; five++){if(one + two*2 + five*5 == x*10){printf("可以用%d个1角加%d个2角加%d个5角凑出得到%d元\n",one,two,five,x);exit = 1;break;}}if(exit) break;}if(exit) break;}return 0;
}//方法2,goto 。goto后面加标号,遇到goto,跳到对应标号所指位置。
int main( )
{int x;int one,two,five;x = 2;				// 凑出2元for(one = 1; one < x*10; one++){for(two = 1; two < x*10/2; two++){for(five = 1; five < x*10/5; five++){if(one + two*2 + five*5 == x*10){printf("可以用%d个1角加%d个2角加%d个5角凑出得到%d元\n",one,two,five,x);goto out;}}}}
out:return 0;
}

—— break和continue只能对它所在的那层循环做。
—— goto后面加标号,遇到goto,跳到对应标号所指位置。

求前N项和

#include 
//求前n项和 f1(n) = 1 + 1/2 + 1/3 + 1/4 +...+ 1/n ;int main( )
{double sum = 0.0;int n = 10;for(int i=1; i<=n; i++){sum = sum + 1.0/i;}printf("f1( %d ) = %f\n",n,sum);
}//求前n项和 f2(n) = 1 - 1/2 + 1/3 - 1/4 +...+ 1/n ;
int main( )
{double sum = 0.0;int n = 10;double sign = 1.0;		// 引入符号,double,就变1.0的符号for(int i=1; i<=n; i++){sum += sign/i;sign = -sign;}printf("f2( %d ) = %f\n",n,sum);
}

整数分解

#include 
// 正序分解整数;输入非负整数,正序输出它每一位数字。如输入12345;输出:1 2 3 4 5;输入700,输出7 0 0;int main( )
{int x;scanf("%d",&x);// 获得和输入整数对应位数的maskint mask = 1;int t = x;while(t>9)		// 当输入为1位时候,mask = 1。{t /= 10;mask *= 10;}// 用mask逐渐减小一位来对整数做商得到最高位,取余得到后几位,// 12345 / 10000 -> 1; 12345 % 10000 -> 2345; 减小mask /10 = 1000// 2345 / 1000 -> 2; 2345 % 1000 -> 345;减小mask /10 = 100// 345 / 100 -> 3; 345 % 100 -> 45;减小mask /10 = 10// 45 / 10 -> 4; 45 % 10 -> 5;减小mask /10 = 1// 5 / 1 -> 5; 5 % 1 -> 5;减小mask /10 = 0do{int d = x /mask;printf("%d",d);if(mask > 9)	// 中间需要有空格 ,最后一位不要空格{printf(" ");}x %= mask;mask /= 10;}while( mask>0 );printf("\n");return 0;
}

最大公约数

输入两个数a和b,输出他们的最大公约数。如输入12 18 输出:6

枚举法

1. 设t为2;
2. 如果u和v都能被t整除,则记下这个t
3. t加1后重复第2步,直到t等于u或v;
4. 那么,曾经记下的最大的可以同时整除u和v的t就是gcd
 

#includeint main()
{int a,b;int min;scanf( "%d %d" , &a,&b);if ( a < b ) {min = a;}else {min = b;}int ret = 0;int i;for ( i = 1; i <= min; i++ ) {if ( a%i == 0 ) {if ( b%i == 0 ) {ret = i;}}}printf("%d和%d的最大公约数是%d.\n",a,b,ret);
}

 辗转相除法。(不是尝试所有数,效率更高)

欧几里得算法_百度百科 (baidu.com)

#include
//辗转相除法求最大公约数
/*1.如果b等于0,计算结束,a就是最大公约数;2.否则,计算a除以b的余数,让a等于b,而b等于那个余数;3.回到第一步。a  b  t12 18 1218 12 612 6 06 0*/
int main()
{int a,b;int t;scanf( "%d %d" , &a,&b);while(b != 0){t = a%b;a = b;b = t;printf("a = %d, b = %d, t = %d\n",a,b,t);}printf("最大公约数是%d.\n",a);return 0;
}


 


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章