C语言讲义(全部)

一、基本知识

  • C语言的特点:结构化(顺序、选择、循环);简洁灵活;类型丰富,表达式强大;(通过指针)可访问硬件;独立性强

  • C语言是(编译型)高级语言,编程解决问题步骤:编辑源程序(.c) -> 编译形成目标文件(.obj)-> 连接得可执行文件(.exe)-> 运行

  • 语句是执行的单位。语句以分号结尾,书写自由。(定义语句,表达式语句,控制语句)

  • C语言程序的基本组成单位是“函数“,是独立的功能模块。

    函数定义:函数头+函数体;函数不可以嵌套定义。

    函数的封闭性:只能访问自己定义的变量。

    函数间的联系:实参传值给形参,函数返回值给调用表达式。

    程序必有唯一的主函数(main),程序的执行总是从主函数开始,并以其结束。(主函数的位置任意)

  • 变量的内存模型:命名的存储对象。先定义后使用的原则。

  • C语言没有输入输出语句,调用函数进行输入和输出。

  • 结构是程序的灵魂,应合理缩进代码,来表达程序的结构。

#include <stdio.h>

int max(int m, int n)
{
    int t;

    if(m>n)
        t = m;
    else
        t = n;

    return t;
}

int main()
{
    int a, b,c, ans;

    scanf("%d %d %d", &a, &b, &c); 
    
    ans = max(a,b);
    ans = max(ans,c);

    printf("The biggest number is %d\n", ans);
    
    return 0;
}

无论简单或复杂,程序大多是 IPO结构(Input - Process - Output)

二、表达式

数据类型

C语言是强类型语言,任何数据(常量、变量、表达式、函数调用等)必有确定的类型

数据类型 决定数据的存储和行为:①数据的表示方式(存储形式)②数据的运算

image-20230126113204859

基本数据类型: 整型(字符型)、浮点型

  • 整型 (short int long)表示整数

    存储空间是有限,因此整型数也是有限的。(根据所占字节不同->表示的范围也不同)-3276832767、-21亿21亿

    C语言规定: ( signed 、unsigned ) short(int) <= int <= long (int)

    short一般占2个字节,int一般占4个字节,long一般占4个字节

    整型以补码形式存储:最高位为符号位,0表示正,1表示负 --> 正数变负数:取反后+1

  • 字符型char

字符型存储的是字符的ASCII编码,占1个字节。-128~127 或 0~255

字符以其编码(整数)存储,因此,在字符型和整型是一致的。字符可视为“1个字节的整型”,即”短短整型“

ASCII编码对应的字符:空格: 32 ;0~9: 48~57 ;A~Z: 65~90 ;az:97122 (小写字母 - 对应大写字母 = 32)

考点:大小写字母转换;数字字符和整数的转换。

  • 浮点数 表示实数 : float double

浮点数是近似表示(尾数+阶码),整数才是精确表示,例:0.2+0.5==0.7(?)

double a = 0.3 + 0.4;
double b = 0.7;

if (a==b)
    printf("ok");
else
    printf("error");	//输出error

// if (fabs(a-b)<1e-6)  则ok   需要#include<math.h>

float:能表示6~7位有效数字(1.23*10^5 --> 有效数字为3位)

double:能表示更多的有效数字(精读更高)

C语言有逻辑类型吗?

任何类型数据均可作为逻辑值, 0表示假,非0 表示真;表达式为假,值为0;表达式为真,值为1

*注意:(-2值为1,0.5值为1,'0'值为1,'\0'值为1)

常量

一、字面值常量的表示

  • 整型常量(占位符 %d %o %x)

十进制(%d), 八进制(0为前缀,%o),十六进制(0x为前缀,%x) 注:输入输出均无前导

后缀u:无符号,后缀L:long

*注意:(018错、34L对、123u对、0017对、0x错) 
  • 浮点型常量 (占位符 %f %lf %e )

小数点 前/后 若为0,0可省略 (eg: 1.0 1. .1 )

默认为double型,加后缀F变为float型 (eg: 2.73F )

考点: 尾数E/e指数 表示: 指数必须整型,尾数与指数均不可省略(eg: 1.2E-6)

*注意:(1E2.0错、E2错、03E2对->八进制)
0.123*10^2		-->		12.3
-0.123*10^5		-->		-12300.0
0.123*10^-2		-->		0.00123

【printf字符串中:%f %e】

【scanf字符串中:%f 对应float; %lf 对应 double 】

  double a;
  scanf("%lf", &a);
  printf("%f", a)
  • 字符常量 (占位符 %c )

1、单引号引起来的一个字符序列

2、转义字符

①单引号引起来,并且以 \ 开头的字符

②'\123' ->八进制对应的123字符

③'\x12' ->十六进制对应的12字符 ('\141' = '\x61' = 'a')

ok: 'a'、'5'、'\n'、'\t'、'\123'、'\0'、'0'、'\\'、'\''、' ' 、'?'、'$'
 
error: 'ab'、"a" 、'''、'\'
  • 字符串常量 (占位符 %s)

双引号括起来的0个或多个字符序列

严格区分字符和字符串,二者类型不同。

二、符号常量

定义格式:#define 符号常量名 符号常量值

#define PI 3.14

注意:预编译(宏定义)语句不是语句,不能加分号

采用符号常量的好处:易理解 ,不出错,易修改

变量

具有标识符(变量名)的存储单元,其值可以改变。变量名、函数名、符号常量名,都是标识符。

标识符的规则:

  • 包含 ”数字、字母、_ “ ,且不能以数字开头
  • 不可使用关键字(任何函数名都不是关键字,都可以作为标识符)
*注意:3a错、a+3错、int错、main对、printf对

变量要 “先定义,后使用”.

定义变量的意义:确定变量名(访问方式)和类型(表示方式),分配内存空间。(变量的三属性:类型 变量名 值)

变量定义语句,可依次定义同类型的多个变量。( int a , b , c ; )

定义的变量,其值不确定,但是可对其初始化。未被初始化的变量其值为随机数

int a=1, b=a+2;		// ok
int a=b,b=1;      	//error 违反“先定义后使用”的原则
int a = b=0;       	//error!注意两个=意义不同

int a;
a = 5.75;
printf("a=%d",a);		//a=5

float a;
a = 123456789;
printf("a=%f",a);		//a=123456715.000000  精读损失

常用的IO库函数

  • printf 和 scanf 函数

  • printf 和 scanf 是输出和输入(扫描)一个格式化的字符串;

  • printf格式字符串中的 “占位符” :

  • %d %o %x (10,8,16进制整型) %c(字符)%f(float 和 double)%e(指数形式)%s(字符串)

  • scanf 格式字符串中的“占位符”:

  • %lf (对应double类型)其它同上.

  • 对应占位符的,printf 是表达式列表,scanf是地址列表

    int a,b;
    scanf("%d %d", &a,&b);
    printf("%d and %d is %d", a, b, a+b);
    

特别注意scanf :(1)变量名前加&;(2)输入时需匹配格式字符串:

scanf("a=%lf,b=%lf", &a, &b); 输入形式: a=12.5, b=23

  • getchar 和putchar用来输入(出)字符

​ ch = getchar(); putchar(ch-32);

  • gets 和puts用来输入、出字符串
scanf("%s", str); // gets(str)  str前不加&
printf("%s", str); //puts(str)

运算符

作用:将多个运算数(常量、变量、函数调用)或表达式 连接起来,构成一个表达式

  • 运算符运算次序:先看优先级,再看结合性

(1)优先级:

  • 初等运算符 > 单目运算符 > 双目(三目)运算符
  • 算术 > 关系 > 逻辑 > ? : > 赋值 > 逗号

(2)结合性 :

  • 双目(赋值除外):从左往右

  • 其余的(单目,三目,赋值) :从右往左

  • 类型转换

    (1) 自动转换:如果参与运算的操作数类型不一致,级别低的自动转为级别高的;

    (2)强制转换:运算符sizeof()

无条件转换:char 转 int 、 float 转 double

  • 副作用:运算如果会改变存储对象(变量),则有副作用。
  1. 单目运算符

    (1)逻辑运算符 !取反.

​ (2)自增++ (自减 -- ) :对左值操作,有副作用

​ 前缀和后缀的区别:副作用一样,但表达式的值不一样。(自增和取值的顺序不同)

​ 前缀使用:先自增,再取值

​ 后缀使用:先取值,再自增

n++;	++n;	n=n+1;	n+=1;	//最终n的值相等
3++; 错	(3+n)++; 错	1+n++; 对
int n=1;
a=n++; 		//得a=1,n=2
a=(n++); 	//得a=2,n=2
int k=5;
t = -k++;		//得t=-5,k=6	等价于	-(k++);	因为是右结合	
//t = (-k)++;	//语法错误,因为-k后,就不是左值了
int a=10, b;
b = a++ * 10;  						//b = ++a * 10;
printf("%d, %d", a, b); //11, 100  	//11, 110

​ (3) sizeof :计算类型所占的字节数,其操作数即可为类型,也可为表达式

注意:sizeof()不是函数

int a=1;
double b=2;
printf("%d",sizeof(a+b)); //输出8,等价于printf("%d",sizeof(double))
printf("%d",sizeof(++a)); //输出4,等价于printf("%d",sizeof(int)),其后a值不变,依旧为1
printf("%d",sizeof(a=10)); //输出4,等价于printf("%d",sizeof(int)),其后a值不变,依旧为1

​ (4)(type) 强制类型转换,注意:括号的位置

double a = 5;  
(int)a%2    // 1     
(int)(a%2)   //error 
(double)1/2  //0.5   
(double)(1/2)  //0
int(3.2)     //error

(5)其它

​ - (取相反数),*(间接访问),&(取地址)

  1. 算术运算符: + - * / %

/ 是重载的运算符:整数除和浮点除,注意分析进行除运算时,左右操作数的类型。

5 / 2 = 2		//整数除
5.0 / 2 = 2.5	//类型自动提升
int a=1;
(double)a/2 ;   //值是0.5
(double)(a/2);  //值是0
1.0*a/2;       	//值是0.5
a/2*1.0;   		//值是0

% 要求左右操作数必须是整型或字符型

double a=10;   
(int)a%3;  		// ok   
(int)(a%3); 	// error
5.0 % 2			//错误
5 % -2 = 1		//由被除数的符号决定
-5 % 2 = -1		
-5 % -2 = -1
  1. 关系运算符 > >= < <= == !=

运算得逻辑值 0或1

0 <= a < = 10    	//值为1
3 == 2+1 == 1+2  	//值为0

C语言表达式,不是数学表达式!要学会从规则出发,像编译器那样去分析表达式

  1. 逻辑运算符 ! && ||

运算得逻辑值 0或1,! 优先级最高,&& 优先级大于||

a>=0 && a<=10    //a介于0~10 
(y%4==0&&y%100!=0)||y%400==0   //判y为闰年,4年润但百年不润,400也润 
!0>2     	//值为0
!(0>2)    	//等价于 0<=2,值为1
*注意:!(-2) 值为0,	2+!5>3 值为0,	!!3 值为1,	!a<10 值为1,	!(a<10) 值不确定,等价于a>=10

&& 和 ||的短路现象:作为二元运算先计算左 L 再右R ;所谓“短路”:只计算L,不(需)计算R

  • L && R 若L为假,R不计算(表达式即为假)。

  • L || R 若L为真,R不计算(表达式即为真)。

    int a = 1  
    if( a>10 && ++a)
            printf("NO ");
       printf("%d", a);
    //因短路,++a 并未执行,以上代码输出 1
    
    int a;
    scanf("%d",&a);
    if (10/a>5)		//输入0,语法错误
    	......
    改:if(a!=0 && 10/a>5)
    

    当表达式 E 作为逻辑值时,E 等价于 E != 0 ,!E 等价于 E == 0

    例如:a && !b 等价于 a != 0 && b==0

  1. 三目(条件)运算符 A ? B : C
(a<b?a:b) <= b		//值为1
c = (a>b?a:b)		//求a b中的较大者
  1. 赋值运算符 =、+=、-=、/=、%= (优先级都是二级)

+=、-=、/=、%= 表示在原来的基础上改变它的值(例:A+=B表示:在 A 的基础上加 B)

  • 赋值表达式具有 “副作用”

  • 赋值运算符的左边必须是 “左值“(非只读的存储对象,例如变量)

  • 赋值表达式也有值,结合性是从右往左(唯一双目右结合),因此 a = b = 2 将变量a和b均赋值为2

    int a=2;
    a *= 3 + 4;		//则a=14,等价于 a *= (3 + 4)
    
    3 + a = 5		//语法错误
    3 + (a = 5)		//a值为5,表达式值为8
    
     int a=10,b;
     a += a *= b = 2 ;
     printf("%d, %d", a, b); // 40,2 注意三个赋值号,三次产生副作用
    
  1. 逗号运算符 优先级最低,表达式的值是右操作数。逗号运算符应用:连接多个(有副作用的)表达式。
int a;
a = 1,2;  		//a的值为1
a = (1,2); 		//a的值为2
a = 1, a++, a += a;  //a为4

例:输入时间 H:M , 然后输入 分钟数n,输出又原时间推移n分钟后的时间.

#include <stdio.h> 

int main()
{
   int h,m,n;

   scanf("%d:%d", &h,&m);
   scanf("%d", &n);

   h += (n+m)/60 ;
   m = (n+m)%60;

   printf("%d:%d", h,m);
   return 0;
}

三、选择分支

结构化程序三结构:顺序 分支 循环

if - else 和 if 语句

if (表达式E)
    语句1
else
    语句2

理解:

  1. 表达式 E 视为逻辑值,可以是任意表达式。与 E != 0 等价
  2. if-else 的两个分支只能是一条语句 ( 注意空语句 ) , if-else 语句也是一条语句。
   int a;   
   scanf("%d", &a);
   if(a=3) 
       a++;         
   else
       a--;    
   printf("%d", a);  // 输出4,无论a输入何值

如果 if - else 中的语句2为空语句,则简化为: if 语句

if(表达式E)
    语句1
    int a=1,b=2, t=0;        
    if(a>b)   t = a;  a = b;   b = t;    
    printf("%d, %d", a, b); // 2,0

复合语句

用 { } 括起一条或多条语句,语法上是一条语句。

例:输入两个整数,将其从小到大输出。

#include <stdio.h>
int main()
{
    int a, b,t;
    scanf("%d  %d", &a, &b);
    
    if(a>b)
    {
        t = a;
        a = b;
        b = t;
    }
    
    printf("%d, %d", a, b);
    return 0;
}

避免二义性的规定

分支结构嵌套时,为避免二义性, C语言规定, else 总是与上面最近的的 if 匹配。

if(a>0)   if(a>b) printf("1");   else     printf("2"); 
// 上面的代码是如下结构:        
    if(a>0)     
        if(a>b)
            printf("1");
        else
            printf("2"); 

if...else...else...是一种书写形式:if-else 的else部分嵌套if-else语句,它可以很清晰地表达多选一的情形。

例: 根据输入的整数,输出 符号函数 的值。

#include <stdio.h>

int main()
 {
     int x, t;
     scanf("%d", &x);
     
     // 下面是一条if-else 语句 实现三分支
     if(x>0)
        t = 1;
     else if(x==0)
        t = 0;
     else
        t = -1;     

     printf("Answer is %d \n", t);
     return 0;
 }     

image-20230217153653444

要求输入x的值,计算并输出y的值(输出结果保留小数点后2位)

#include <stdio.h>
#include <math.h>

int main()
 {
     double x, ans;
     scanf("%lf", &x);
     
     if(x<1)
        ans = fabs(x);
     else if(x<10)		// (x>=1 && x<10)
        ans = pow(x,3.6);      
     else
        ans = sqrt(x);
    
    /*
    if(x<5)
        y = x+5;
     else if(x<10)
        y = x*x +0.3*x;
     else  
        y = 5*x-10;       
    */      

     printf("Answer is %.2f \n", ans);
     return 0;
 }

例:百分制成绩转五级制等级。

    int a; //百分制分数
    char rank;
    scanf("%d", &a);
    
    // 五选一
    if(a>=90)
        rank = 'A';
    else if(a>=80)
        rank = 'B';
    else if(a>=70)
        rank = 'C';
    else if(a>=60)
        rank = 'D';
    else
        rank = 'E';

    printf("Rank is %c\n",rank );

注意:以上结构是多分支选其一,依次检查逻辑条件,一旦为真则执行对应语句即跳出。

例:输入三边长度(整数),若能构成三角形则输出其面积,否则输出"Error”。

#include <stdio.h>
#include <math.h>
int main()
{
   int a,b,c;
   double s,area;
   scanf("%d%d%d",&a,&b,&c);
   
   if(a+b>c && a+c>b && b+c>a)
   {
   	s = (a+b+c)/2.0;
   	area = sqrt(s*(s-a)*(s-b)*(s-c));
   	printf("%.2f",area);
   }
   else
   	printf("Error");
   return 0;
}

例: 输入公里数,停车等候分钟数,根据以下收费规则,计算车费

  • 起步里程为3公里,起步费10元;
  • 超起步里程后10公里内,每公里2元;
  • 超过10公里以上的部分加收50%的回空补贴费,即每公里3元;
  • 营运过程中,因路阻及乘客要求临时停车的,按每5分钟2元计收(不足5分钟则不收费)。
int s,time;
    int fee = 10;
    
    scanf("%d %d", &s, &time);
     
    if(s>3)
    {
        if(s<=10)
            fee += (s-3)*2;
        else
            fee += 14+(s-10)*3;
    }
    
    fee += time/5*2;
    printf("%d", fee);

例:分段计价:每月用电量50千瓦时(含50千瓦时)以内的,电价为0.53元/千瓦时;超过50千瓦时的,超出部分的用电量,电价上调0.05元/千瓦时。请编写程序计算电费

   scanf("%d", &kw);

    if(kw<=50)
        fee = kw*0.53;
    else
        fee = 50*0.53+(kw-50)*0.56;
    
    printf("%.2f",fee);

条件运算符

A ?B :C

意义:若A非0 ,值为B,否则为C

注意:A被视为逻辑值 等价于 A!=0

t = a>b ? a:b ;  	// a,b较大者 赋给t
t = a>=0 ? a:-a;  	// a的绝对值 赋给t
a ? 1 : 2    		// 如果a!=0,值为1,否则2

int max(int m, int n)  // 返回m,n之较大者
{
        return m>n?m:n;
} 

//符号函数
	t = x>0?1:(x==0?0:-1)	//括号可去掉
//a,b,c最大值=> t
 	t = a>=b && a>=c ? a : (b>=c ? b:c);
// 上述表达式与下面的代码等价
 if(a>=b && a>=c)
        t = a;
  else
        if(b>=c)
           	t = b;
         else
            t = c;

switch 语句

计算switch后的表达式,将其值与case 标号进行比较,从匹配的标号进入执行。

  1. switch 与 break语句配合,方才实现多分支。(考卷上一定会缺失break)
  2. 标号必须是(整型或字符)常量,且不可以重复。
  3. default 可省略
“标号”:形如"3:" "a:", 放在语句前来标识此语句的位置,“标号” 必须是(整型或字符)常量表达式。
switch语句执行过程:计算switch后的表达式,将其值与"case 标号"进行比较,从匹配的标号进入执行。
强调:
1. switch与break语句配合,方才实现多分支; (考卷 上一定会缺失break) break跳出switch结构
2.标号不可以重复;可以连续放置多个标号;
3. default可省略

四、循环结构

while语句

while ( E ) 语句1

当E为真,则执行循环体(语句1)

理解:

  1. 表达式 E 视为逻辑值,可以是任意表达式。与 E != 0 等价
  2. 循环体只能是一条语句 ( 注意空语句 ) , while 语句也是一条语句。

image-20220309170254366

学会分析循环

  1. 虽每次循环执行的代码相同,但是各次循环的 “状态” 各异。循环的状态由相关变量构成,分析循环需要跟踪循环的状态,即跟踪各变量的值。
  2. 每一次循环的终点,是下次循环的起点。

例:分析下面的代码,建议记录进入循环以及循环中,变量的变换。

int main()
{
    int a=1,b=10;

    while(a<=b)
    {
        printf("%d, %d\n", a, b); // 1,10  3,10  4,8  6,8 
        if(a%3)
            a++;
        else
            b-=2;
        a++;
    }

   	printf("Over: %d, %d\n", a, b);  // Over: 7,6
    return 0;
}

例:输入一个整数N,输出N的位数。

int main()
{
    int n,k;
    scanf("%d", &n);
    
    k=0;  // 循环计数
    while(n!=0)
    {
        n /= 10;  
        k++;
    }
    printf("%d", k);      
    return 0;
}

例:输入一个整数 N,输出N的各位数之和。

#include <stdio.h>
int main()
{
    int n,sum = 0;
    scanf("%d", &n);
    
    while(n!=0)
    {
    	sum += n % 10;
        n /= 10;
    }
    printf("sum is %d ", sum);
    return 0;   
}

例:输入一行字符,统计字母,数字,空格,其他字符的数量。

分析:一行字符,即以‘\n’为结束标识的若干字符。

int main()
{
  char ch;
  int alpha=0,dig=0,space=0,other=0; 
  ch = getchar();
  while(ch!='\n')
  {
      if(ch>='a'&&ch<='z' || ch>='A'&&ch<='Z')
        	alpha++;
      else if(ch>='0' && ch<='9')
        	dig++;
      else if(ch==' ')
        	space++;
      else
          other++;

      ch = getchar();
  }

  printf("%d, %d, %d, %d", alpha, dig, space, other);
}

for语句

  • 适合用于表达 “确定”次数 的循环。
  • for ( 表达式1;表达式2;表达式3 ) 语句1
  • 三个表达式由两个 ;确定位置。三个表达式均可以省略。

image-20220309170427091

例:输入两个整数,输出二者之间所有非3倍数之和。

int main()
{
    int i,m,n,sum=0,t;
    scanf("%d %d", &m, &n);
    
    if(m>n)
    { t = m; m = n; n = t; }  
    
    for(i=m; i<=n; i++)
    {
        if(i%3!=0)
           sum += i;
    }
    
    printf("%d", sum);
    return 0;
}

例:输出所有水仙花数

int main()
{
    int n,a,b,c;
    
    for( n=100; n<=999; n++ )
    {
        c = n%10;
        b = n/10%10;
        a = n/100;

        if(a*a*a+b*b*b+c*c*c == n)
            printf("%d ", n);
    }
    return 0;
}

例: 输出1000以内的所有完全数。所谓完全数,它等于所有(不包括自身)的因数之和

int main()
{
    int n,k,sum;

    for(n=1; n<=1000; n++)
    {
        sum=0; // 每个n的因数累加器
        for(k=1; k<n; k++)
        {
            if(n%k==0)
                sum += k;
        }
        if(sum==n)
            printf("%d ", n);
    }
    return 0;
}

例:定义函数:输出高度为n的金字塔

分析:规律图像都是二重循环,外循环控制各行,内循环输出各行字符。

void printStar(int n)
{
    int i,k;
    
    for(i=1;i<=n; i++)
    {
        for(k=1;k<=n-i;k++) // n-i个空格
            putchar(' ');

        for(k=1;k<=2*i-1;k++) // 2i-1个*
            putchar('*');

        putchar('\n'); // 记得换行
    }
}

break 和 continue 语句

  • break :提前跳出循环:出现在循环体中,跳出所在的循环。
  • continue :“短路”,提前进入下次循环,它只能出现在循环体。
// 输出 1 2 4 5 7 8 10
    for(i=1;i<=10 ;i++)
    {
        if(i%3==0) 	// 能被3整除
           continue;
        printf("%d ", i);
    }

// 输出1 2 
 	for(i=1;i<=10 ;i++)
  	{
        if(i%3==0) 	// 能被3整除
           break;
        printf("%d ", i);
  	}

    i=1;
    while( i<=10 )
    {
        if(i%3==0) 	// 能被3整除
        {
            i++;  	// 没它,死循环!!!
            continue;
        }
        printf("%d ", i);
        i++;
    }

循环编程的思想

  1. 递推:确定初始状态,每次循环在前一个状态基础上运算,得到新的状态 ....
  2. 穷举,用循环来测试所有可能的情形

例:求1-n的阶乘和

int main()
{
    int n;
    int i,t,k,sum;
    
     scanf("%d", &n);
    
	// 利用二重循环求各阶乘的和
//    sum = 0;
//    for(i=1;i<=n;i++)
//    {
//        t = 1; // i!总是从1开始累乘
//        for(k=1;k<=i;k++)
//             t *= k;
//
//        sum += t;
//    }


    // 下面是利用的递推的思想求解 
     t = 1;     //0!=1
     sum = 0;
     for(i=1;i<=15;i++)
     {
         t *= i;    // i! = (i-1)! * i
         sum += t;
     }
     
     printf("%d ", sum);
     return 0;
}

例: a + aa + aaa + aaaa + aaaaaa + ... 求1个a到n个a的和。

int main()
{
    int n,a,t,i,sum;
    scanf("%d %d", &a, &n);
    t = 0; 		// 0个a是0
    sum = 0;
    for(i=1; i<=n; i++)
    {
        t = t*10+a;  // 递推
        sum += t;
    }
    printf("%d ", sum);
    return 0;
}

例:输出Fibonacci数列 1 1 2 3 5 8 13 21....的前30项,5个一行。

int main()
{
    int a, b,c,i;
    a = b = 1; //起点

    printf("%8d%8d", 1,1);

    for(i=3; i<=30; i++)
    {
        c = a+b;
        printf("%8d", c);
        
        if(i%5==0)
            putchar('\n');

        a = b;
        b = c;
    }
    return 0;
}

例:2/1 3/2 5/3 8/5 13/8 .... 求其前20项之和i

int main()
{
    double z,m,sum;   
    int i;

    z = 2; m = 1; 	//递推起点
    sum = 2; 
    
    for(i=2; i<=20; i++)
    {
        int t = m;
        m = z;
        z = t+z;
        
        sum += z/m;
    }
    
    printf("%f", sum);    
    return 0;
}

题型:多变量组合的穷举

百钱百鸡问题。公鸡单价5,母鸡单价3,小鸡三只价1。 百钱买百鸡,问公鸡,母鸡,小鸡各多少?

分析: 多重循环穷举三个变量的所有组合。因小鸡,和母鸡确定后,公鸡数量即确定,所以二重循环即可。注意,小鸡数量是3的倍数,直接枚举小鸡数量。

int main()
{
    int g,x,m;	// 公鸡,小鸡,母鸡数量
    for(x=0; x<=100; x+=3) 	// 小鸡数 0 3 6 9 ...
    {
        for(m=0; m<=100-x; m++)
        {
           g = 100-x-m;
           if( 5*g+3*m+x/3 == 100 ) //满足百钱
              printf( "%d %d %d\n", g, x, m ); //输出一组满足条件的解
        }        
    }   
    return 0;
}

五、数组

数组就是在内存中,以连续存储空间来存储多个元素。

一维数组

定义数组 int a[ N ];

  • 数组长度N 必须是整型常量;
  • sizeof(a) / sizeof(int) 的值是 N
  • 数组的初始化时,数组长度默认就是初始化的个数;可以部分初始化,后面隐式初始为0.
 int a[] = {3,4,5,6,7};    
 printf("%d ", sizeof(a)/sizeof(int) ); // 5

访问数组元素:通过 a[ 下标 ] 来访问 ,下标从 0 - N-1

类型1:通过扫描数组,对数组元素进行统计和查询(查询,求和,计数,最...)

例:依次输入10个实数,将其最大和最小元素交换后输出。

#include <stdio.h>
#define N 10  // 符号常量
int main()
{
    double a[N],t;
    int i,maxIdx,minIdx;  // 分别记录当前最大(最小)元素的下标

    for(i=0; i<N; i++)   // 依次输入数组元素值
        scanf("%lf", &a[i]);

    maxIdx = minIdx = 0;   // 令0号元素最大(最小)
    for(i=1; i<N; i++)
    {
        if(a[i]>a[maxIdx])
            maxIdx = i;

        if(a[i]<a[minIdx])
            minIdx = i;
    }

    if(maxIdx != minIdx)  // 交换
    {
        t = a[maxIdx]; a[maxIdx] = a[minIdx]; a[minIdx] = t;
    }

    for(i=0; i<N; i++)  // 输出调整后的数组
        printf("%8.2f",a[i]);

    return 0;
}

例:输入10个整数,求其中奇数的均值。

#include <stdio.h>
#define N 10  // 符号常量

int main()
{
    int a[N],t,oddSum;
    int i,maxIdx,minIdx,cnt;  // 分别记录当前最大(最小)元素的下标

    for(i=0; i<N; i++)   
        scanf("%d", &a[i]);
        
    oddSum = 0;  // 奇数的累加器
    cnt = 0;    // 奇数的计数器
    for(i=0;i<N;i++)
    {
        if( a[i]%2 )
        {
            oddSum += a[i];
            cnt++; 
        }       
    }
    
    if(cnt)
       printf("%.2f", 1.0*oddSum/cnt);   // 注意均值是浮点数  
 
    return 0;
}

类型二:数组转置

例: 输入9个数,将其逆序后输出。

#include <stdio.h>
#define N 4  // 符号常量

int main()
{
    int a[N],t;
    int i,j;

    for(i=0; i<N; i++)
        scanf("%d", &a[i]);
 
   	// i和N-1-i号元素是对称的
   	for(i=0; i<N/2; i++)
    {     // 交换a[i] 和 a[N-1-i] 
        t = a[i];
        a[i] = a[N-1-i];
       	a[N-1-i]=t;
   	}
   
    // i和j号元素是对称的
    for(i=0,j=N-1; i<j; i++,j--)
    {  //通过赋值交换 
        int t = a[i];
        a[i]=a[j]
        a[j]=t;
    }

    for(i=0; i<N; i++)
        printf("%d ", a[i]);

    return 0;
}

类型三:通过递推,生成数组中的各元素。

例:输出Fibonacci数列的前20项,三个一行。

#include <stdio.h>
#define N 20  // 符号常量

int main()
{
    int i;
    int a[N] = {1,1}; 
    for(i=2;i<N;i++)
        a[i] = a[i-1] + a[i-2];

    for(i=0;i<N;i++)
    {
        printf("%7d ", a[i]);
        if((i+1)%3 == 0 )
            putchar('\n');
    }

    return 0;
}

类型四: 排序

例: 冒泡排序

// 长度为n的数组a递增排序
void sort(int a[],int n)
{
    int i,j;
    
    for(i=1; i<n; i++)  // n-1趟     
        for(j = 0; j<n-i;  j++) //相邻元素比较,逆序则交换       
            if(a[j]>a[j+1])
            {
                t = a[j];
                a[j]=a[j+1];
                a[j+1]=t;
            }     
}

选择排序

思路:

  • 在a[0... n-1]中选择最小元素与a[0]交换,

  • a[1... n-1]中选择最下元素与a[1]交换

    ...

  • 上述操作进行n-1趟

#include <stdio.h>
#define N 10

int main()
{
    int a[]= {4,26,1,11,67,92,32,47,21,42},t;
    int i,j,kmin;

    for(i=0; i<N-1; i++)
    {
        kmin=i;  // 先认为i号元素最小
        for(j=i+1; j<N; j++)
        {
            if(a[j]<a[kmin])
                kmin=j;
        }
        if(i!=kmin)
        {
            t= a[i];
            a[i]=a[kmin];
            a[kmin]=t;
        }
    }

    for(i=0; i<N; i++)
        printf("%4d", a[i]);

    return 0;
}

二维数组

  • 逻辑上,二维数组 是 二维的,通过行列下标进行访问。
  • 本质上,二维数组 是“元素为一维数组的” 一维数组。

定义3行4列数组:int a[3][4],则 a[0] a[1] a[2]分别是一行,即长度为4的一维数组。

定义二维数组时:

  1. 永远不能省略列数;

  2. 可以一维和二维的形式初始化,此时可省略行数。

    int a[][5]={1,2,3,4,5,6,7} // 2*5个元素
    int a[][5] ={{1,2},{1,2,3}}; // 2*5个元素
    

    题型1:通过按行,按列访问进行查询,统计

    例:按行输出,按列输出

    	int a[3][2] = {1,2,3,4,5,6};
    	int i,j;//行和列下标
    
    	// 按行输出
    	for(i=0;i<3;i++) // 行下标作外循环变量
    	{
           	for(j=0;j<2;j++)
               	printf("%d ", a[i][j]);
            
           	putchar('\n'); 
       	}
    
       	// 按列输出
       	for(j=0;j<2;j++) // 列下标作外循环变量
       	{        
           	for(i=0;i<3;i++) 
               	printf("%d ", a[i][j]);
            
           	putchar('\n');
       	}           
    

    例:输出二维数组a[N][M]各行及各列的平均值

    #include <stdio.h>
    #define N 20
    #define M 5
    
    int main()
    {
        int a[N][M];    
        double sum;    
        int i,j;  
        
        for(i=0;i<N;i++)
        {
            sum=0;
            for(j=0;j<M;j++)
                sum += a[i][j];
            printf("%f ", sum/M);
        }
        
        for(j=0;j<M;j++)
        {
            sum=0;
            for(i=0;i<N;i++)
                sum += a[i][j];
            printf("%f ", sum/N);
        }
        return 0;
    }
    

    例:求二维数组中最大元素

#include <stdio.h>
#define N 3
#define M 4
int main()
{
    int a[N][M]={{1,2,3},{4,17},{26}};
    int i,j,maxi,maxj;

    maxi=maxj=0;

    for(i=0;i<N;i++)     
        for(j=0;j<M;j++)        
            if(a[i][j]>a[maxi][maxj])
            {
                maxi=i; maxj=j;
            }
    printf("%d ", a[maxi][maxj]);
    return 0;
}  

题型二 转置

例:二维数组(N*N方阵)转置。

分析:a[i][j]与 a[j][i]关于主对角线对称,下三角形的a[i][j]与 上三角形的a[j][i]交换

   for(i=0; i<N; i++)
     {
         for(j=0;  j<i;  j++)   // a[i][j]只取下三角中的元素
         { // a[i][j]与 a[j][i]交换
             int t = a[i][j];
             a[i][j] = a[j][i];
             a[j][i] = t;
         }
     }

例: 求N*N方阵的主副对角线元素之和。

     s = 0;
     for(i=0;i<N;i++)      
         s += a[i][i]+ a[i][N-1-i];         
      
     if(N%2) // N是奇数,减去重复计算的中间元素
        s -= a[N/2][N/2];

题型三,利用二维数组递推

利用二维数组,输出杨辉三角形前10行

int main()
{
     int a[10][10];
     int i,j;

     for(i=0;i<10;i++)
     {
         for(j=0;j<=i;j++) // 下三角的元素
         {
             if(j==0||i==j)  // 首列和主对角线元素值为1
                a[i][j]=1;
             else
                a[i][j] = a[i-1][j] + a[i-1][j-1]; // 等于正上方和左上方元素之和

             printf("%4d",a[i][j]);
         }
         putchar('\n');
     }
    return 0;
}

六、函数

函数是“独立”的功能模块,是C语言程序的基本单位。

函数的概念

  • 函数不能嵌套定义;
  • 函数只能访问本函数定义的局部变量;
  • 调用函数时,实参表达式的值传递给形参变量。
  • 返回时,函数值返回给 “函数调用表达式”。
// 判断n是否为素数,是则返回1,否则返回0
int isSx(int n)
{
    int i;
    int flag=1;

    for(i=2; flag!=0 &&i<n; i++)
    {
        if(n%i==0)        
            flag=0;          
    }
    return flag;
}

// 将100-200内所有偶数表示为两个素数之和
int main()
{
    int n, a;

    for(n=100; n<=200; n+=2)
        for(a=2; ; a++)
        {
            if( isSx(a) && isSx(n-a) )// 调用函数判a和n-a是素数
            {
                printf("%d  %d\n", a, n-a);
                break;
            }
        }

    return 0;
}
#include <stdio.h>

void disp(int b[],int n)
{
    int i;
    for(i=0;i<n;i++)
        printf("%d ", b[i]);
}

//  从右边左边顺序查找,如果查找成功返回其下标,失败返回-1
int search(int a[],int n,int e)
{
    int i;
    for(i=n-1;i>=0;i--)
    {
        if(a[i]==e)
            break;
    }
    return i;
}

// 折半查找
int bisearch(int a[],int n , int x)
{
    int low=0,high=n-1,mid;

    while(low<=high)
    {
        mid = (low+high)/2;
        if(a[mid]==x)
            return mid;
        else if(x>a[mid])
            low = mid+1;
        else
            high = mid-1;
    }
    return -1;
}

int main()
{
    int a[10]={1,3,5,7,8,10,13,14,17,18};
    int x = 9;

    int k = bisearch(a,10,x);

    if(k>=0)
        a[k]+=100;

    disp(a,10);
    
    return 0;
}

求两个整数的最大公约数

// 辗转相除法
int gcd(int a, int b)
{
   c= a%b;
   
   while(c!=0)
   {
       a = b;
       b = c;
       c= a%b;
   }
   return b;
}


// 根据最大公约数的定义
int gcd1(int a, int b)
{
    int k;
    for(k=a;   ; k--)
    {
        if(a%k==0&& b%k==0)
            break;
    }
    return k;
}

int lcb(int a, int b)
{
     return (a*b)/gcb(a,b);
}
 

局部、全局和静态变量

  1. 函数中定义的变量为“局部变量”,访问性,生存期都是局部的。
  2. 函数外定义的变量为“全局变量”,访问性,生存期都是全局的。

例:通过全局变量调用函数 divmod 获取两个整数的商和余数。

分析:函数只能返回一个值。考虑借助两个全局变量,如同公告的“快递柜“一样,在函数间传递数据

int a,b; // 全局变量

void divmod(int m,int n)
{
    a = m/n;
    b = m%n;
}

int main()
{
    divmod(15,7);
    printf("%d %d", a, b);

    return 0;
}
  1. 全局变量和局部变量同名时,局部遮蔽全局。
  2. 函数内定义的static变量,访问局限于此函数,但生存期是全局的。
  3. 全局变量和static 变量,初始值为0. 局部变量初始值不确定
  4. 函数的return表达式与函数定义类型不一致时,以函数定义的类型为准。
int  fun(int n)
{
    static int t = 1; // 静态变量,生存期是全局的。函数多次调用,也不会重复初始化

    t *= n;
    return t;
}

int main()
{
    int i;
    for(i=1;i<=5;i++)
       printf("%d\n", fun(i) ); // 输出1! 2! 3! 4! 5!

    return 0;
}

7 . 如果程序文件中,调用前未定义函数,则需要声明函数,声明即函数头;

  • 头文件包含了相关函数的声明,常见的头文件 stdio.h math.h string.h

递归函数

递归函数:调用自身的函数。

理解:虽然是调用同一个函数,但每次调用时,函数的环境是独立的(比如局部变量等)

递归调用的要点:

  1. 递归要有出口:何时不再递归
  2. 递归模型:待解问题通过调用“子问题”来解决
void  printStar(int n)
{
    if(n==0)
        return; // 出口
    putchar('*');
    printStar(n-1);  // 递归
}

int main()
{
    printStar(5);
    return 0;
}

例:递归函数,计算 ,n!,汉诺塔问题

//x的n次方
int fun(double x, int n)
{
    if(n==0)
        return 1 ;
    return x*fun(x,n-1);    
}

// n的阶乘
int jc(int n)
{
    if(n>0)
        return n*jc(n-1);
    else
        return 1;    
}

//将n个盘片从from经temp移动到des 
//高度为n的塔,上面是高度n-1的塔作为子问题递归处理,下面大盘片直接移动
void hanoi(int n,char from,char temp, char des)
{
    if(n>0)
    {
        hanoi(n-1,from ,des, temp);
        printf("%c-->%c\n", from, des);
        hanoi(n-1,temp,from, des);
    }
}

int main( )
{
    hanoi(5,'A','B','C');
}   

七、指针

指针和变量

指针就是“存储对象”的地址 。

  • 访问 “存储对象” 两种方式:直接访问(变量名),间接访问(指针)
  • 两个相关的运算符 &(取地址) *(间接访问)
  • 定义指针变量:
int n=10,*p=&n;   // 定义整型变量n,指针变量p( 初始化p指向n )
*p = 5; // 等价于 n = 5

强调 :以上两个 * 完全不相同 :

  • 定义语句中,说明定义的变量p是“指针”类型;

  • 表达式中,是“间接访问”运算。

  • p是int *类型,表达式 *p 是int 类型

  • 定义指针变量,若未初始化,则为 ”野指针”, 不能对野指针间访。

  • 0(NULL)指针表示没指向任何对象,不允许间接访问的

    分析指针及 其指向的对象之间关系时,将指针抽象为指向存储对象的 “箭头指针”

    例子:分析以下代码

    int main()
    {
        char s1[100];
        scanf("%s", s1) ; //s1是char*类型
        char s[]={'a','b','c','\0'};//等价于 char s[]= "abc";
        s[1] = '\0';
        puts(s); // 输出a 
        
        char *p = "abc";
        puts(p);// 输出abc
        p[1] = 'B'; // 错误,p指向的常量区,只读不可写 
    }
    

指针和函数

函数不能直接访问 其它函数的局部对象;但可以传递对象的地址,使函数能够间接访问。

void swap(int *p,int *q)
{
    int  t;  // t是int类型哦
    t = *p;    *p = *q;    *q = t;   // 交换的是*p和*q哦!
    return;
}

// 假交换:虽接收了地址,但并未间访
void swap(int *p ,int *q)
{
    int  *t; 
    t = p;    p = q;    q = t;   // 交换的是p和q哦!
    return;    
}

int main( )
{
    int a=1,b=2;

    swap(&a,&b);
    printf("%d %d", a ,b); // 2 1
}
// 函数通过p和q两个指针变量,将两个运算结果,赋值给main中的变量
void divmod(int m,int n,int *p,int *q)
{
    *p = m/n;
    *q = m%n;
    return;
}

int main( )
{
    int a,b; // 主函数定义变量

    divmod(10,3,&a,&b); // 传递操作数和a,b的地址,divmod就可能访问a和b

    printf("%d %d", a ,b); // 2 1
}
  • 实参如果是变量名,那么传递时变量的值。被调函数无法访问实参的变量。
  • 实参如果时变量的地址,那么被调函数可以通过间访来访问实参相应的变量。

指针 和 一维数组

  • 数组名是数组 首元素的地址,是地址“常量”。
  • 当指针p指向数组元素时:p+n( p-n )就是指向p往右(左)n个元素,
  • 指针可以进行关系运算,两个指针相减等于它们相差的元素数量。
  • *(p+n) 记作 p [n] ,因此p并不一定是数组名!
  • 区分 ++*p *p++++*p是p间访的数组元素自增,*p++是指针自增。
void disp(int a[], int n)
{
    int *p = a+n;// 尾元素后的元素,作为结束的标识
    while(a!=p) //  或while(a<p)    或 for(i=0;i<n;i++)    
        printf("%d ", *a++); //这里的a是指针变量     
}

int main()
{
    int a[7] = {10,20,30,40,50,60,70},i,*p=a;

    // 依次输出数组元素
    for(i=0;i<7;i++)
         printf("%d ", *p++ );  //*a++ 错误,因为a是常量
    p = a;
    while(p < a+7) 
        *p++ = 0;

    disp(a+2,3); // 输出30 40 50

    return 0;
}

指针和二维数组

  • int a[N][M]是元素为行(即int [M])的一维数组,因此a是”行指针 ”类型:int (*)[M]
  • 访问二维数组元素 a[i][j] 等价于 *(*(a+i)+j),形式变换而已。
void disp(int a[][4],int n) //(int (*a)[4],int n)
{
    int i,j;
    for(i=0;i<n;i++)
        for(j=0;j<4;j++)
            a[i][j]= i*j;   //  (*(a+i)+j) = i*j;     
}

int main()
{
     int a[2][4], (*p)[4] = a;     
     fun(a,2); // fun(p,2);

    return 0;
}

二维数组本质是一维数组,按行存储。因此可以用一维形式进行访问。

int a[2][3]={{1,2,3},{4,5,6}},*p,i;
    
    p = &a[0][0];  // p = a 错误
    for(i=0;i<2*3;i++)
    {
        p[i] = 0; //*p++ = 0;         
    } 

字符串

  • 字符串 就是以\0为结束标识的字符数组。

  • 区分 字符串的长度(strlen函数) 和存储字符串数组的字节数(sizeof运算符)。

  • 形如 “abcd”是字符串常量, 是 “常量区 ” 的字符数组。”abcd” 就是数组名!!!

  • 所谓 “字符串” , 就是字符数组的某个指针,因此字符串(常量或字符数组)的类型是 char *

  • 区分以下两种操作:通过赋值,让指针变量指向某字符串;通过调用strcpy函数,将字符串拷贝到字符数组。

    int main()
    {
        char s1[100];
        scanf("%s", s1) ; //s1是char*类型
        char s[]={'a','b','c','\0'};//等价于 char s[]= "abc";
        s[1] = '\0';
        puts(s); // 输出a 
        
        char *p = "abc";
        puts(p);// 输出abc
        p[1] = 'B'; // 错误,p指向的常量区,只读不可写 
    }
    

    例:字符串常见的操作

// 输出字符串
void Puts(char *s)
{
    while( *s!='\0' )
        putchar(*s++);
}

// 返回字符串长度
int Strlen(char *s)
{
    int n=0,i=0;
    while(s[i++]!='\0') n++;    //while(*s++ !='\0') n++;
    return n;
}

// 将字符串s拷贝到字符数组d
void Strcpy(char *d,char *s)
{
    while(*s!='\0')
        *d++ = *s++;
    *d = '\0'; // 别忘了结束标记    
}

int main()
{
    int n;
    char s[10];
    
    n = Strlen("1234");
    printf("%d ", n);// 4
    Strcpy(s,"abc");
    Puts(s);
    
    return 0;
}

头文件 string.h 中的字符串函数

puts gets strlen strcpy strcmp strcat

 sizeof("abc")  //4 占4个字节
 strlen("abc")  //3 长度为3
     
 char s[100],*p;
 gets(s);
 puts(s);

strcpy(s,"hello");  // ok ,注意 s="hello"错误
strcpy(p,"hello");  gets(p) ;  //均为错误,因p是野指针
p = s;  strcpy(p,"hello");  // p指向数组s  ok,拷贝到数组s中

例:输入N个字符串,按照字典顺序递增排序后输出。

分析:二维字符数组,每行存储一个字符串。采用冒泡排序,字符串的比较和复制调用strcmp和strcpy函数

#include <string.h>
#define N 4

int main()
{
    char s[N][100]; //二维数组每行存储一个字符串
    int i,j;

    for(i=0; i<N; i++)
        gets(s[i]);

    for(i=0; i<N; i++)
        for(j=0; j<N-i; j++)
            if(strcmp(s[j],s[j+1]))
            {
                char t[100];
                strcmp(t,s[j]); 
                strcmp(s[j],s[j+1]);
                strcmp(s[j+1],t);
            }

    for(i=0; i<N; i++)
        puts(s[i]);

    return 0;
}

指针数组

元素为指针的数组就是指针数组。字符指针数组,用来管理多个字符串。

例:依次输出N个字符串

#include <string.h>
#define N 4
int main()
{
    char *ps[N]={"Beijing","Shanghai","Guangzhou","Wuhan"};
    int i;
    for(i=0;i<N;i++)
        puts(ps[i]);
}

二级指针

指针的指针。

指针数组名,即为二级指针。

八、结构体

结构体是自定义类型,自定义类型 使用如同基本类型。

  • 利用成员运算符(. )访问结构体变量的成员。因其成员也可为复合类型,务必注意访问的层次。

  • -> 运算符是两个运算的合成:对结构体进行间访,然后访问其成员。

  • 与基本类型同样:用结构体类型可以定义变量名、数组、指针,也可以初始化和赋值;(作为实参时)传值或传地址或 从函数返回。

  • 结构体类型不支持关系运算

  • 定义结构体的同时定义变量(数组,指针)。此时,结构体可以是匿名的。

    struct Position
    {
        double x;
        double y;
    };
    
    // 传结构体值,输出点
    void disp(struct Position d)
    {
        printf("<%.1f, %.1f>", d.x,d.y);
    }
    
    // 点移动后,返回结构体
    struct Position move(struct Position d, double deltax, double deltay)
    {
        d.x += deltax;
        d.y += deltay;
    
        return d;
    };
    
    // 传结构体地址,进行交换
    void swap(struct Position *p, struct Position *q)
    {
        struct Position t;
        t = *p;
        *p = *q;
        *q = t;
    }
    
    int main()
    {
        int i;
        //定义结构体变量,指针,数组
        struct Position d1= {1,2},d2,*p = &d1,a[5]= {{1,2},{3,4},{5,6}};
    
        d1.x += 10;  // 成员运算符
        p->x += 10;  // 等价于 (*p).x
        
        disp(d1); // 或disp(*p);
    
        d2 = move(d1,10,20);
        swap(&d1, &d2);
    
        // 下面输出 <1.0, 2.0> <3.0, 4.0> <5.0, 6.0> <0.0, 0.0> <0.0, 0.0>
        for(i=0; i<5; i++)
            disp(a[i]);
    
        return 0;
    }
    
    struct Date
    {
        int year;
        int month;
        int day;
    };
    
    struct  Student  
    {
        int id;   //学号
        char name[10]; //姓名
        //struct Date birthday;
        struct  //匿名类型
        {
            int year;
            int month;
            int day;
        }birthday;
        int scores[5];  //  成绩表
    }stu = {100,"Xiaoming",{2000,3,23},{70,80,75,90}};
    
    int main()
    {
        int i;
        struct Student t;
        
        printf("%d",stu.id);
        printf("%s", stu.name);
        printf("%d-%d-%d", stu.birthday.year, stu.birthday.month, stu.birthday.month);
        for(i=0;i<5;i++)
            printf("%d ", stu.scores[i] );  
    
        t.id = 100;
        strcpy(t.name,"Laowang"); //!!!!!!!!!!
        t.birthday.year = 1999;
        t.birthday.month = 3;
        t.birthday.day = 25;    
        t.scores[0] =80;
        
    }                     
    

    一个简单的例子

    struct  
    {
        int k;
        struct 
        {
            int r;
            char  s[10];
        }t;
    }obj;
    
    int main()
    {
        obj.k = 100;
        obj.t.r = 99;
        strcpy( obj.t.s , "hello");
    
        return 0;
    }
    

九、文件

文件分为:文本文件(t),二进制文件(b)

文件指针

FILE *

  • FIle 是系统定义结构体类型,包括各种文件状态信息;

  • fopen 用来打开一个文件,返回 FILE *

  • 打开文件的模式: r w a r+ w+ t(默认为文本) b(二进制)

  • 程序运行时,系统自动建立3个FILE * stdin stdout stderr

  • EOF 是文件结束的标识

    int main()
    {
        FILE *fp;
    
        fp = fopen("abc.txt","wt");
    
        if (fp == NULL)
        {
            printf("error");
            exit(-1);
        }
    
        fprintf(fp,"hello");
    
        fclose(fp);
        
        return 0;
    }
    
// 统计a.txt中的字符数量
int  main()
{
    FILE  *fp;
    int count=0;

    fp = fopen("a.txt","rt");
    if(  fp == NULL )
    {
        printf("erro.");
        exit(-1);
    }
    while(  fgetc(fp) != EOF  )
        count++;
    fclose(fp);
    printf("count is %d.\n",count);

    return 0;

}

其它

宏替换 是预编译语句,它是简单的字符串替换,不分析语法。

带参数的宏替换

#define F(a,b) a*b

Add(2, 5+3) 被替换为 2 * 5 + 3

#define F(a,b) ((a)* (b)) 方才不会出错,否则很可能预想不到的结果!

类型定义 typedef

将类型取一个类型别名。

typedef
struct  
{
    int x;
    int y;
} Pair;

typedef int  INTGER
    
Pair a;
INTGER b;

枚举类型

enum Week
{
    Mon=1,Tue,Wed,THU,FRI,SAT,SUN
};

共用类型

各成员公用存储空间

union T
{
    int a;
    char t[10];
}d;
sizeof(d) // 10个字节

链表

malloc函数

void *malloc(int size);
  • 申请一块“指定大小” 的内存区域,并以void*类型返回分配的内存区域的基地址。
  • void * 可以强制转为任意基类型的指针,因此是“万能指针”
int n, *p,i;
scanf("%d", &n);

p = (int*)malloc(sizeof(int) *n);

for(i=0;i<n;i++)
     p[i] = i;

free(p);

image-20220530165551597

“附带头结点”的空链表

  1. 为方便计,链表一般附带头结点;
  2. 链表的每一个结点都是动态分配的(malloc)

建立附带头结点的链表h

#include <stdio.h>
#include <stdlib.h>

typedef int T;

typedef struct Node
{
    T data;
    struct Node *next;    
}Node;

// 链表类型就是Node*
int main()
{
    // 建立附带头结点的空链表h
    Node * h;
    h = (Node *)malloc(sizeof(Node));
    h->next = NULL;

    return 0;
}

追加结点

输入n个数据,构造结点,装载数据,依次追加到空链表h

void create(Node * h, int n)
{
    int i;
    T e;
    Node *p,*tail = h; // tail是尾指针,初始头结点即末尾结点   
    for(i=0; i<n;i++)
    {
        // 建立新结点
        scanf("%d", &e);
        p = (Node *)malloc(sizeof(Node));
        p->data = e;
        p->next = NULL;
        
        // 新结点插到尾部
        tail->next = p;
        tail = p;    // 新结点成为尾结点,为下次循环更新尾指针   
    }
}

依次输出结点值

依次输出链表h中的元素值

void dispList(Node *h)
{
    Node *p;
    for(p=h->next;  p!=NULL;   p=p->next)
    {
        printf("%d ", p->data);
    }
}

查找结点

链表h中查找值为x的元素,查找成功返回指针,不成功返回NULL

Node * search(Node *h, T x)
{
    Node *p;
    
    for(p=h->next; p!=NULL;  p=p->next)
    {
        if(p->data == x)
            break;
    }
    return p;
}

删除指定的结点

删除链表h中,值为x的结点。删除成功返回1,否则返回0

思路:删除某元素,应该查找其前驱p,从而转换删除p的后继结点。

int  del(Node *h, T x)
{
   Node *p,*t;
   
   // 令p指向待删结点的前驱 
   p=h; 
   for(p=h; p->next!=NULL;  p=p->next)
   {
        if(p->next->data==x)
            break;
   }
   
   // 删除p的后继结点   
   if(p->next)  
   {
       t = p->next; // t是待删结点
       p->next = t->next;
       free(t);  // 回收结点的内存 
       return 1;      
   }  
   return 0;
}

主函数

主函数里建立空链表,然后调用链表的各种操作 。

int main()
{
    // 建立附带头结点的空链表
    Node * h,*p;
    h = (Node *)malloc(sizeof(Node));
    h->next = NULL;

   // 输入5个数据,建立链表
    create(h, 5); 
    dispList(h);
  
   // 查询值为3的元素,如果查找成功,则将其增加100
    p = search(h,3);
    if(p)
        p->data += 100;
   
   // 删除值为3的元素
    del(h,3);
    dispList(h);

    return 0;
}
posted @ 2022-12-30 21:43  王智刚  阅读(55)  评论(0编辑  收藏  举报