前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【期末复习】⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)

【期末复习】⚡考试月来临!C语言复习,这一篇带你逃离挂科区!(完结)

作者头像
小丞同学
发布2021-08-16 16:19:35
8700
发布2021-08-16 16:19:35
举报
文章被收录于专栏:小丞前端库

我又回来啦!距离上一篇博文发出不到4个小时,这篇博文又启动了,希望顺顺利利,快逃离挂科区!?

如果没有看过上节的可以链接跳转噢~C语言复习(上),这篇会接着上一篇写噢~

如果需要源文件,可以私信我噢!

4. 函数

函数是具有一定功能的一个模块,所谓函数名就是给该功能起了一个名字。

注意:函数就是功能。每一个函数用来实现一个特定的功能。函数的名字应反映出它代表的功能,这样代码的可读性会大大提升

记得在上一篇中有这么一句话,“一个C程序可由一个主函数和若干个其他函数构成。” C语言是一门完全面向过程的语言,在程序设计中要善于利用函数,以减少重复代码的编写,尽量的减少代码冗余,这样也能提高代码的可维护性,也更便于实现模块化的程序设计。

4.1 定义一个函数

通过了上面的解释,相信已经加深了对函数的理解以及对函数的作用有了进一步的认识。

定义一个函数需要包括以下几个内容:

  1. 指定函数的名字,以便以后的函数调用
  2. 指定函数的类型,即函数的返回值的类型
  3. 指定函数的参数的名字和类型,以便在调用函数的时候传递参数
  4. 书写函数的功能,这是函数的核心内容
4.1.1 定义一个无参函数

当函数不需要接受用户传递的数据时,不需要带参数

代码语言:javascript
复制
返回值类型 函数名() {
    功能
}

下面我们来定义一个计算1加到100的函数

代码语言:javascript
复制
int sum()
{
    int i, sum = 0;
    for (i = 1; i <= 100; i++)
    {
        sum += i;
    }
    return sum;
}

通过将计算的结果保存到sum中,最后通过return语句返回给函数的调用者,返回值的类型是int

4.1.2 无返回值函数

有些函数不需要返回值,只需要执行代码即可,我们可以通过void来指定返回值的类型

代码语言:javascript
复制
void hello() }{
    printf("hello world");
}

这个函数没有返回值,它的功能就是输出hello world,类型void表示空类型或者无类型,大多数意味着无return语句

4.1.3 定义有参函数

当函数需要接收用户传递的数据,那么定义时就需要带上参数

代码语言:javascript
复制
int max(int x, int y) {
	int z;
	z = x > y ? x : y;
	return z;
}

上面的函数功能是求两个数中较大的那个数。在主函数调用时,将数据传递给形参x,y,在函数体内判断两个书中较大的数,并通过return语句返回值返回给函数的调用者

注意:

  1. 参数的数据说明可以省略,默认值是int类型
  2. 函数名称需要遵循标识符命名规范
  3. 函数需要先定义后使用,如果函数写在了main函数后面,需要将函数的声明写到main中函数调用之前
  4. 在C语言中不允许函数的嵌套定义

4.2 调用函数

调用函数的方式非常简单,以调用上面计算两数中最大值为例

代码语言:javascript
复制
c = max(a, b);

这样我们就能实现了函数的调用,将 a,b 传给max函数,函数执行完毕后返回值z的值赋值给c,这样c就得到了a,b中较大数的值

下面我们编写一个程序来练练手

输入两个整数,要求输出其中值较大者,使用函数来实现

首先我们先编写max函数,用来返回两个数中的较大者

代码语言:javascript
复制
int max(int x, int y) {
	int z;
	z = x > y ? x : y;
	return z;
}

接下来我们编写主函数

代码语言:javascript
复制
int main()
{
    int a, b, c;
    printf("请输入两个数\n");
    scanf("%d,%d", &a, &b);
    c = max(a, b);
    printf("max is %d", c);
    return 0;
}

在主程序中接收两个用户输入的数据a,b,通过函数返回大的数,实现功能

注意:程序从上向下执行,当碰到函数名后,把值传给调用函数,当程序得到了返回值或调用函数结束,再继续往下执行。

4.3 形参和实参的区别

在上一部分中,我们复习了如何定义和调用函数。

如果函数是一个加工厂的话,那么函数的参数就是工厂的原材料,返回值就是经过加工的产品。

重要

  1. 形参只有在函数被调用时才会分配内存,调用结束后,会立刻释放内存,因此形参变量只有在函数内部有效,不能在函数外部使用。
  2. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。如果会自动类型转换,或者进行了强制类型转换,那么实参类型也可以与形参类型不同。
  3. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参。也就是改变形参不会影响实参的值。

注意:传数值,形参的变化不会改变实参的变化;传地址,形参的变化就有可能改变实参所对应的值

4.4 函数的嵌套调用

函数不能嵌套定义,但可以嵌套调用,也就是在一个函数的定义或调用过程中允许出现对另外一个函数的调用。这部分的内容过于简单就不过多阐述,就是在一个函数里调用另一个函数,无限套娃

4.5 函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为递归调用,这也是C语言的特点之一。递归函数在解决许多数学问题上起了至关重要的作用,比如计算一个数的阶乘、生成斐波那契数列,等等。

注意:递归必须要有一个结束的条件,否则可能会无限的调用死循环

下面通过递归来输出斐波那契数列的第n项

代码语言:javascript
复制
#include <stdio.h>
int Fib(int n)
{
    if (n == 0)
    {
        return 1;
    }
    else if (n == 1)
    {
        return 1;
    }
    else
        return Fib(n - 1) + Fib(n - 2);
}
int main()
{
    int n, ret;
    printf("请输入n:");
    scanf("%d", &n);
    ret = Fib(n);
    printf("%d\n", ret);
    return 0;
}

重要语句return Fib(n - 1) + Fib(n - 2);通过return再次调用这个函数,直至到达结束的条件。

4.6 全局变量和局部变量

4.6.1 局部变量

定义在函数内部的变量称为局部变量,它的作用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。

代码语言:javascript
复制
int name(int a)
{
    int b, c;
    return a + b + c;
}
int main()
{
    int x, y;
    return 0;
}
  1. 在 main 函数中定义的变量也是局部变量,只能在函数中使用。main 函数也是一个函数,与其他函数平等地位
  2. 实参给形参传值的过程也就是给局部变量赋值的过程
  3. 可以在不同的函数中使用相同的变量名,它们表示不同的数据,分配不同的内存,互不干扰。(我偷偷的把它理解为js中的块级作用域)
4.6.2 全局变量

声明在函数外部的变量称为全局变量,它的作用域是整个作用域,也就是整个文件

4.6.3 练习题

输入长方体的长宽高求它的体积以及三个面的面积。(编写一个函数实现)

代码语言:javascript
复制
#include <stdio.h>

int s1, s2, s3; //面积

int volume(int a, int b, int c)
{
    int v; //体积
    v = a * b * c;
    s1 = a * b;
    s2 = b * c;
    s3 = a * c;
    return v;
}

int main()
{
    int v, length, width, height;
    printf("输入长宽高: ");
    scanf("%d %d %d", &length, &width, &height);
    v = volume(length, width, height);
    printf("v=%d, s1=%d, s2=%d, s3=%d\n", v, s1, s2, s3);
    return 0;
}

采用了三个全局变量,来记录三个面的面积,这样通过main函数可以直接通过访问全局变量来获取到对应面积的值,通过返回值来得到体积v

注意:建议在不必要的情况下不要使用全局变量(这个在其他语言中也是同样的)

原因:

  1. 全局变量在程序的全部执行过程中都要占用存储单元,而不是仅在需要时才开辟单元
  2. 它使函数的通用性降低了,不利于模块化编程
  3. 降低了代码的可读性

4.7 内部函数和外部函数

  1. 不能被其他源文件调用的函数称谓内部函数 ,内部函数由static关键字来定义,因此也叫静态函数,如static int max()
  2. 能被其他源文件调用的函数称谓外部函数 ,外部函数由extern关键字来定义,形式为extern int max()
  3. 在没有指定函数的作用范围时,系统会默认为外部函数
代码语言:javascript
复制
// hello.c文件
 void hello()    
{
   printf("hello world");   
}
//main.c文件
#include<stdio.h>
#include "hello.c"
int main() {
    hello();
    return 0;
}

4.8 练习题

第一题

在 C 语言中,有关函数的说法,以下正确的是 。 A. 函数可嵌套定义,也可嵌套调用 B. 函数可嵌套定义,但不可嵌套调用 C. 函数不可嵌套定义,但可嵌套调用 D. 函数不可嵌套定义,也不可嵌套调用

答案:C

第二题

有一个函数原型如下所示,则该函数的返回类型为( ) 。 abc(float x,float y) A. void B. double C. int D. float

答案:C 默认int

第三题

C语言中,以下叙述中错误的是(  )。 A) 主函数中定义的变量是全局变量 B) 同一程序中,全局变量和局部变量可以同名 C) 全局变量的作用域从定义处开始到本源程序文件结束 D) 局部变量的作用域被限定在其所定义的局部范围中

答案:A 函数同级

5. 指针

每一个变量都有一个内存位置,可使用 & 取地址符来访问它的内存地址,它表示了在内存中的一个地址。?

指针也就是内存地址,指针变量是用来存放内存地址的变量。就像其他变量或常量一样,必须在使用指针存储其他变量地址之前,对其进行声明。

格式为:类型 *变量名

代码语言:javascript
复制
int    *ip;    /* 一个整型的指针 */
double *dp;    /* 一个 double 型的指针 */
float  *fp;    /* 一个浮点型的指针 */
char   *ch;    /* 一个字符型的指针 */

不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。

形象的说:一个房间的门口挂了一个房间号1304,这个1304就是房间的地址,或者说1304指向这个房间。因此把地址形象化的称为指针。

指针变量可指向任意一种数据类型,但不管指向的数据占用多少字节,一个指针变量占用四个字节。

5.1 指针变量

存放地址的变量是指针变量,它用来指向另一个对象

注意:在定义指针变量时,必须确定指针类型。例如:int变量的指针需要用int类型指针来存储。

区分指针变量和指针,指针是一个地址,而指针变量是存放地址的变量

5.1.1 使用指针变量

通过指针变量访问整型变量

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    int a = 100;
    int *pointer;
    pointer = &a;
    printf("*pointer的值是%d", *pointer);
    return 0;
}

先定义一个整型变量,再定义一个指针变量,指向这个整型变量,通过访问指针变量可以找到它所指向的变量,从而得到变量的值

5.1.2 定义指针变量

定义指针变量需要在变量名前面加星号*,例如

代码语言:javascript
复制
int *try;

*表示这是一个指针变量,int表示该指针变量所指向的数据的数据类型是int类型

代码语言:javascript
复制
int a = 100;
int *p = &a;

在定义指针变量p时同时对它进行初始化,将变量a的地址赋予它,此时p就指向了a

注意:

  1. 指针变量p需要接收的是一个地址,因此需要使用&取地址符,来获取a的地址
  2. 定义指针变量时必须带*号,给指针变量赋值时不能带*
  3. 指针变量p的类型是int *而不是int噢,它们是完全不同的,考试避坑噢~
5.1.3 引用指针变量
  1. 给指针变量赋值
代码语言:javascript
复制
p = &a; //a的地址赋值给指针变量p

指针变量 p 的值是变量 a 的地址,p 指向 a

  1. 引用指针变量指向的变量
代码语言:javascript
复制
p = &c;
printf("%d",*p);

printf("%d",*p);的意思是:一整数形式输出指针变量 p 指向的变量的值

代码语言:javascript
复制
*p = 1;

表示将整数1赋值给 p 所指向的变量,即c = 1

  1. 引用指针变量的值
代码语言:javascript
复制
printf("%o",p);

作用是以八进制形式输出指针变量 p 的值,如果 p 指向了 a ,输出的就是 a 的地址

*和&:这两个符号的作用是相对的,可以理解为&是取变量的内存地址,*号是取地址上的变量值,也就是解引的作用

代码语言:javascript
复制
int a;
*&a = a;

*&a可以理解为*(&a)&a表示取变量 a 的地址,*(&a)表示取这个地址上的数据

5.2 指针变量作为函数参数

在前面的函数部分中,我们有说到

“传数值,形参的变化不会改变实参的变化;传地址,形参的变化就有可能改变实参所对应的值”

指针变量作为函数参数就是传地址的情况,这能帮助我们解决一些问题。一个典型的例子就是交换两个变量的值

当我们想要交换两个变量时,我们可以声明一个swap交换函数,交换两个变量的值,但是,采用常规的值传递的方式是行不通的

代码语言:javascript
复制
#include <stdio.h>
void swap(int a, int b){
    int temp;  
    temp = a;
    a = b;
    b = temp;
}
int main(){
    int a = 1, b = 2;
    swap(a, b);
    printf("a = %d, b = %d", a, b);
    return 0;
}

因为函数内部的a,b都是局部变量,它们占用着不同的内存,改变swap函数中的a,b值,不会影响到main函数中a,b的值


采用用指针变量作为函数参数,就可以解决这个问题,因为参数的传递是内存地址,外部函数,直接通过修改内存地址上的值,来完成操作

代码语言:javascript
复制
#include <stdio.h>
void swap(int *p1, int *p2){
    int temp; 
    temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}
int main(){
    int a = 1, b = 2;
    swap(&a, &b);
    printf("a = %d, b = %d", a, b);
    return 0;
}

5.3 通过数组引用指针

5.3.1 数组元素的指针

?所谓数组元素的指针就是数组元素的地址

可以用一个指针变量指向一个数组元素,例如

代码语言:javascript
复制
int a[3] = {1, 2, 3};
int *p;
p = &a[0]

引用数组元素可以采用下标法a[1],也可以采用指针法,指针法占用内存更少,运行速度更快

在数组部分,我们知道数组名称代表了首元素的地址,因此我们可以直接写p = a,来指向数组的第一个元素

5.3.2 在引用数组元素时的运算

在指针已经指向一个数组元素的情况下,可以进行下列运算

p + 1:指向同一数组中的下一个元素

p - 1:指向同一数组的上一个元素

注意:p + 1不是简单的数值上的加一,而是加上一个数组元素所占的字节数,如float类型数组一个元素占4个字节,p的值就加4,也就指向了下一个元素

如果p的初值指向的是数组的第一个元素a[0]那么可想而知p + i就能访问到数组元素a[i]的地址,例如p + 9的值是&a[9],同样的我们获取到了了数组元素的地址,就可以采用*号来得到它的值,如*(p + i) = a[i]

5.3.3 练习题

?通过指针变量输出整型数组a的10个元素

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    int *p, i, a[10];
    p = a; //a的地址
    printf("请输入10个整数:\n");
    for (i = 0; i < 10; i++)
    {
        scanf("%d", p++);
    }
    p = a;//由于指针p在遍历的过程改变了,因此要重新赋值
    for (i = 0; i < 10; i++, p++)
    {
        printf("%d ", *p);
    }
    return 0;
}

在遍历时,采用了p++,在每一次遍历结束后,将指针p指向数组的下一位

5.4 指针数组和数组指针

指针数组:它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身决定。也叫做存放指针的数组

数组指针:它是一个指针,它指向一个数组。在32 位系统下永远是占4 个字节,也叫做指向数组的指针

5.5 通过指针引用字符串

在前面我们也有提到过“c语言中没有字符串变量”,但是可以通过字符数组和字符指针的方式存储字符串

我们先从一个简单的题目入手

通过字符指针变量输出一个字符串

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    //定义一个字符指针来存储字符串
    char *string = "i am ljc";
    // 输出
    printf("%s", string);
    return 0;
}

在上面的代码中成功的输出了i am ljc,我们可以知道,在输出string时,字符指针最开始指在了字符串的第一个字符,又因为字符串在内存中占据的是连续的内存空间,在输出控制符%s下,系统会自动的输出字符串的第一个字符,然后让字符指针指向下一个字符,直至遇到\0结束。

字符指针变量和字符数组的比较

  1. 字符数组由若干个元素组成,每个元素中放一个字符,而字符指针变量中存放的是地址
  2. 赋值方式不同,可以对字符指针变量赋值,而不能对数组名赋值
  3. 存储单元不同,编译时字符数组分配若干存储单元,以存放各元素的值,而对字符指针变量,只占4各字节(不同编译器可能不同)
  4. 指针变量的值是可以改变的,而字符数组名代表一个固定的值,不能改变

5.6 指针作为函数返回值

当函数的返回值是一个指针时,把这个函数称为指针函数

代码语言:javascript
复制
#include <stdio.h>
#include <string.h>
char *longStr(char *str1, char *str2){
    if(strlen(str1) >= strlen(str2)){
        return str1;
    }else{
        return str2;
    }
}

上面的代码定义了一个返回长度较长字符串的函数

在使用指针函数时要注意,函数运行结束后会销毁在它内部定义的所有局部数据,函数返回的指针不要指向这些数据

5.7 练习题

?第一题

有定义:int x,*p;,能使指针变量p指向变量x的语句是(  )。 A) *p=&x;  B) p=&x; C) *p=x;  D) p=*&x;

答案:B

?第二题

若有语句int *p, a=10; p=&a; 下面均代表地址的一组选项是()。 A. a, p, *&a B. &*a, &a, *p C.*&p, *p,&a D.&a, &*p, p

答案:D

?第三题

若已定义char s[10];则在下面表达式中不表示s[1]地址的是()。 A. s+1 B. s++ C. &s[0]+1 D. &s[1]

答案:B

?第四题

若定义:int a=511, *b=&a;则printf("%d\n", *b);的输出结果为: A. 无确定值 B. a的地址 C. 512 D. 511

答案:D

?第五题

下面程序中输出几个* A. 9 B. 5 C. 6 D.7

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    char *s = "\ta\018bc";
    for (; *s != '\0'; s++)
    {
        printf("*");
    }
}

答案:C \0是转义字符表示后面是个8进制数


定 义

含 义

int *p;

p 可以指向 int 类型的数据

int **p;

p 为二级指针,指向 int *类型的数据

int *p[n];

p 为指针数组。[ ] 的优先级高于 *,所以应该理解为 int *(p[n]);

int (*p)[n];

p 为二维数组指针

int *p();

p 是一个函数,它的返回值类型为 int *

int (*p)();

p 是一个函数指针,指向原型为 int function() 的函数

6. 结构体

结构体从本质上来讲是一种自定义的数据类型,但是这种数据类型比较复杂,它是由 intcharfloat 等多种基本类型组成的

从前端js的角度去思考,我会把结构体形象为js中的对象

这部分没有写链表的内容,在我之前的博文中有写到了用js实现链表的完整操作思路,实际上思路都一致,只是语法不同,就不过多阐述,有兴趣的可以看之前的博文:一文带你拿下前端必备数据结构 – 链表 !!

6.1 定义和使用结构体

6.1.1 建立结构体类型

可以使用结构体struct来存放一组不同的数据类型

下面采用结构体来存储一个成员的个人信息

代码语言:javascript
复制
struct people
{
    char *name;  //姓名
    int num;     //学号
    int age;     //年龄
    float score; //成绩
};

people为结构体的名字,里面包含了4个成员。结构体成员的定义方式与变量和数组的定义方式相同,只是不能初始化。

特别注意:结构体后的花括号需要打分号

6.1.2 定义结构体变量

在上面我们定义了一个结构体类型,我们可以用它来定义变量

代码语言:javascript
复制
struct people s1, s2;

这样我们就定义了两个变量s1,s2,它们的类型都是people类型,它们都有4个成员组成。形象的说:定义出来的people就相当于一个模板,该类型的变量都会有它的特性

代码语言:javascript
复制
struct people
{
    char *name;  //姓名
    int num;     //学号
    int age;     //年龄
    float score; //成绩
} s1, s2;

也可以直接在结构体最后定义变量

结构体的各个成员中在内存中是连续存储的,和数组相似,但是由于结构体中的数据类型复杂,各个成员间存在着间隙,因此存在着结构体内存对齐的问题!

6.1.3 读写结构体成员的值

使用点号.获取单个成员,也可以给成员赋值。.号叫做成员访问运算符!

学前端的现在可以舒一口气了,这个和对象太像了,其实学习一门编程语言当你学到了它的思想后,学其他的语言都会很轻松的,所以各位一定要先学踏过门槛~冲冲冲

通过这样的方式可以获取成员的值,也可以赋值

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    struct people
    {
        char *name;  //姓名
        int num;     //学号
        int age;     //年龄
        float score; //成绩
    };
    struct people my;
    my.name = "LJC";
    my.num = 1023;
    my.age = 19;
    my.score = 100;
    printf("姓名:%s,年龄:%d,学号:%d,分数:%.1f", my.name, my.age, my.num, my.score);
    return 0;
}

运行结果:姓名:LJC,年龄:19,学号:1023,分数:100.0

这样我们就实现了对结构体变量的赋值。

? 结构体是一种自定义的数据类型,是创建变量的模板,不占用内存空间。只有在创建结构体变量的时候才会占用内存空间!!!

6.2 结构体数组

结构体数组指数组中的每个元素都是结构体,这样就非常的方便了,我们可以把一个班级的学生放在一个结构体数组里,这样一个班级学生都绑定上了结构体中的成员

代码语言:javascript
复制
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    int score;  //成绩
}class[45];

定义了一个有45个学生的班级

读写的方式和结构体变量相同,在结构体数组定义的同时也可以进行初始化,也可以不给出数组的长度,如:

代码语言:javascript
复制
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    int score;  //成绩
}class[] = {
    {"ljc",3213,19,100},
    {"ljc",3223,19,99}
};

结构体数组的使用也很方便,访问赋值即可

代码语言:javascript
复制
class[0].score = 99;

6.3 结构体指针

结构体指针就是指向结构体的指针,一个结构体变量的起始地址就是这个结构体变量的指针。

定义结构体指针的一般形式为struct 结构体名 *变量名

代码语言:javascript
复制
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    int score;  //成绩
} my = {"ljc", 1023, 19, 100};// 定义了一个结构体变量my
struct stu *pmy = &my; // 定义结构体指针pmy,赋予my的地址,让它指向my

也可以在定义结构体的同时定义结构体指针:(多见几种形式考试才不会生疏噢~)

代码语言:javascript
复制
struct stu{
    char *name;  //姓名
    int num;  //学号
    int age;  //年龄
    int score;  //成绩
} my = {"ljc", 1023, 19, 100}, *pmy = &my;

? 特别注意:结构体的变量名和数组名不同,数组名在表达式中会被转换为数组指针指向数组的第一个元素,而结构体变量名不会因此需要带上&取地址符噢~

6.3.1 获取结构体成员

有两种获取的方法,一种是采用成员访问运算符,另一种是采用->运算符,称为箭头,可以通过结构体指针直接取得结构体成员pmy -> num

示例:通过结构体指针来输出结构体成员的值

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    struct people
    {
        char *name;  //姓名
        int num;     //学号
        int age;     //年龄
        float score; //成绩
    } my = {"LJC", 1023, 19, 100}, *pmy = &my;
    printf("姓名:%s,年龄:%d,学号:%d,分数:%.1f", (*pmy).name, (*pmy).num, pmy->age, pmy->score);
    return 0;
}

特别注意:在使用第一种方法的时候,一定要有括号,因为.的优先级高于*,如果去掉意义就不一样了

6.3.2 结构体数组指针使用

结构体数组指针的使用和结构体指针基本一致,其中定义的结构体指针ps是指向struct stu结构体数据类型的指针变量,在 for 循环中给指针赋初值,指向数组的第一个元素,输出结束后,指针指向数组的下一位即ps++

代码语言:javascript
复制
#include <stdio.h>

struct stu
{
    char *name;  //姓名
    int num;     //学号
    int age;     //年龄
    float score; //成绩
} student[] = {
    {"LJC", 1023, 19, 100},
    {"LIN", 1021, 19, 110},
    {"JC", 1020, 19, 120},
},
  *ps;//定义结构体数组指针

int main()
{
    //求数组的长度
    int len = sizeof(student) / sizeof(struct stu);
    printf("Name\tNum\tAge\tScore\t\n");
    for (ps = student; ps < student + len; ps++)
    {
        printf("%s\t%d\t%d\t%.1f\n", ps->name, ps->num, ps->age, ps->score);
    }
    return 0;
}

输出结果

6.3.3 结构体指针做函数参数

通过示例来复习吧:计算学生成绩的平均分

注意:通过传入结构体指针,这样可以减少内存的占用,在传入结构体数组时,会将它所占用的内存单元的内容全部作为参数传递给形参,在函数调用的过程中,形参也需要占用内存,这样对于内存的开销就会很大

代码语言:javascript
复制
#include <stdio.h>
struct stu
{
    char *name;  //姓名
    int num;     //学号
    int age;     //年龄
    float score; //成绩
} student[] = {
    {"LJC", 1023, 19, 100},
    {"LIN", 1021, 19, 110},
    {"JC", 1020, 19, 120},
};

void average(struct stu *ps, int len)
{
    int i;
    float average, sum = 0;
    for (i = 0; i < len; i++)
    {
        sum += (ps + i)->score;
    }
    printf("sum = %.1f\n平均分=%.2f", sum, sum / len);
}
int main()
{
    //求数组长度
    int len = sizeof(student) / sizeof(struct stu);
    average(student, len);
    return 0;
}

输出结果

6.4 共用体类型

共用体是一种特殊的数据类型,不同于结构体的是它允许在相同的内存位置存储不同的数据类型,因此我们可以从名字上来浅析,它们共用同一块内存空间

6.4.1 定义和使用共用体类型

定义的方式和使用的方式和结构体基本一致,定义结构体采用struct,定义共用体采用union关键字,定义格式为:

代码语言:javascript
复制
union 共用体名 {
    成员列表
};

看是不是基本一样呀,相信你应该都忘记了吧~

结构体和共用体的区别在于:结构体的各个成员会占用不同的内存,互相之间没有影响;而共用体的所有成员占用同一段内存,相互之间会有影响。

共用体的内存大小取决于成员中占据内存最大的内存,例如定义以下共用体:

代码语言:javascript
复制
union data{
    int a;
    char b;
    double c;
};

占用内存最大的是double占用了8个字节,所以共用体所占的字节数就是8,它们该共用体在内存中的排列方式如图:

大致是这个意思,画的有点丑

下面我们通过一个示例来演示共用体成员在内存的分布

代码语言:javascript
复制
#include<stdio.h>
//定义共用体
union data {
    int i;
    char ch;
};
int main() {
    union data a; //定义共用体变量
    printf("%d,%d\n",sizeof(a),sizeof(union data)); //输出 8 8 
    a.i = 0x62;
    printf("%X,%c\n",a.i,a.ch);
    a.ch = '9';
    printf("%X,%c\n",a.i,a.ch);
}

输出结果

在代码的第10行我们给共用体成员i赋值0x62,会被存入内存中的第一个字节中,当给ch赋值为'9'时,字符9的ascII码为57,对应的16进制就是0x39,因此第一个字节中会被改写成39,这样成员i的值也被改变了

6.5 枚举类型

在高级程序设计中是这样解释的:所谓枚举,就是指把可能的指一一列举出来,变量的值只限于列举出来的值的范围

枚举类型的定义

声明枚举类型采用enum开头,格式如下:

代码语言:javascript
复制
enum typeName {valueName1,valueName2,...}

示例:列出一个星期

代码语言:javascript
复制
enum Weekdday {sun, mon, tue, wed, thu, fri, sat};

这样我们就声明了一个枚举类型enum Weekday

这样我们就可以定义这个枚举类型的变量enum Weekday workday,这样就定义了一个枚举变量

注意:枚举变量和其他变量不同,它们的值只限于花括号中的指定值之一

每一个枚举元素都代表一个整数,默认是0,1,2,3....,在上面的定义中sun = 0 mon = 1 依次下去,我们也可以手动设定每一个枚举元素的值

代码语言:javascript
复制
enum Weekday {sun = 7,mon = 1,tue, wed, thu, fri, sat} workday;

指定了sun的值为7,mon为1,依次递增,则sat为6

几点注意事项:

  1. 枚举列表中的标识符的作用范围是全局的,不能定义相同的变量名
  2. 枚举元素又叫枚举常量,不能赋值,例如sun = 7是不合法的,只能在定义枚举类型时设定好
  3. 枚举元素不再是变量,不能使用&来获取它们的地址

6.6 typeof 声明新类型名

typeof关键字,可以用来为类型取一个新的名字,例如我可以给int类型取名为a之后我要定义新的int类型时,就可以使用a来声明

代码语言:javascript
复制
typeof int a;
a num;

当然实际中我们并不会这样用,因为这样的新名字别人根本看不懂,假设在一个程序中,需要用一个变量来计数,我们可以这样做:

代码语言:javascript
复制
typeof int Count; // 指定Count代表int
Count i; // 用count定义变量i

这样可以一目了然的知道它是用于计数的

注意:用typeof声明的新类型名,只是一个别名,原类型名仍然可以正常使用

typeof用于处理复杂的类型会非常的好用,例如结构体,共用体这些

代码语言:javascript
复制
typeof struct stu
{
    char *name;  //姓名
    int num;     //学号
    int age;     //年龄
    float score; //成绩
} Student;

像上面的代码中就定义了一个结构体,并且使用typeof取了别名student,因此在我们需要定义一个 stu 结构体变量时,我们只需

代码语言:javascript
复制
Student ljc;

这样我们就定义了一个结构体变量,上面的代码语义就非常的清晰,学生ljc,代码读起来写起来都会比较方便

下面我们看多几个来熟悉以下

命名一个新的类型名代替数组

代码语言:javascript
复制
typeof int Num[100];
Num a; //定义了一个整型数组a,相当于 int a[100]

代替指针类型

代码语言:javascript
复制
typeof char *String;
String p,s[10];// p为字符指针变量,s为字符指针数组

代替指向函数的指针类型

代码语言:javascript
复制
typeof int (*Pointer)();
Pointer p1,p2; //p1,p2为Pointer类型指针变量

总结:按定义变量的方式,把变量名换成新的类型名,并且在最前面加typeof,就声明了新类型名代表原类型

一步步来

代码语言:javascript
复制
// 1.按照定义变量的方式写 
int a[100];
//2. 把变量名换成新类型名
int Num[100];
//3. 在前面加typeof
typeof int Num[100];
//over

6.7 练习题

?第一题

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    struct sk
    {
        int a;
        float b;
    } data, *p;
    p = &data;
}

以上代码块对data中的成员a的正确引用是: A)(*p).data.a B) (*p).a C)p->data.a D) p.data.a

答案:B

?第二题

代码语言:javascript
复制
#include<stdio.h>
struct name1{
	char str;
	short x;
	int num;
}A;
int  main()
{
    int size =  sizeof(A);
    printf("%d\n",size);
    return 0;
}

上面的代码输出结果是多少____

答案:8 考点:结构体内存对齐

?第三题

和上一题不一样噢,将第5行和第六行位置调换

代码语言:javascript
复制
#include <stdio.h>
struct name1
{
    char str;
    int num;
    short x;
} A;
int main()
{
    int size = sizeof(A);
    printf("%d\n", size);
    return 0;
}

输出多少____

答案:12

7. 文件的输入输出

关于文件就不做过多的阐述,直接来写相关的操作

7.1 打开和关闭文件

7.1.1 打开文件

fopen来实现打开文件,调用方式为:fopen(文件名,使用文件方式);

例如:fopen("a1","r)表示打开文件a1使用读入的方式。

注意:fopen()函数会获取文件信息,包括文件名、文件状态、当前读写位置等,将这些信息保存到一个 FILE 结构体变量中,然后将该变量的地址返回。

FILE是<stdio.h>头文件当中的一个结构体,所以可以直接使用

代码语言:javascript
复制
FILE *fp = fopen("demo.txt", "r");

判断是否打开成功

如果打开失败会返回一个空指针,因此可以通过返回的指针来判断文件是否打开成功

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    FILE *fp = fopen("demo.txt", "r");
    if (fp == NULL)
    {
        printf("打开失败");
    }
    else
    {
        printf("打开成功");
    }
}

fopen函数的打开方式

控制读写权限的字符

打开方式

说明

“r”

**以“只读”方式打开文件。**如果文件不存在,出错。

“w”

**以“写入”方式打开文件。**如果文件不存在,那么创建一个新文件

“a”

**以“追加”方式打开文件。**如果文件不存在,出错。

“r+”

以“读写”方式打开文件。如果文件不存在,出错。

“w+”

以“写入/更新”方式打开文件。如果文件不存在,建新文件。

“a+”

以“追加/更新”方式打开文件。如果文件不存在,出错。

控制读写方式的字符

打开方式

说明

“t”

文本文件。如果不写,默认为"t"。

“b”

二进制文件。

读写权限和读写方式可以组合使用,读写方式要放在读写权限中间或尾部,例如:

  • 将读写方式放在读写权限的末尾:“rb”、“wt”、“ab”、“r+b”、“w+t”、“a+t”
  • 将读写方式放在读写权限的中间:“rb+”、“wt+”、“ab+”

从上面的字符中,我们可以知道文件打开方式由6个字符组成,它们的含义分别是:

  • r:读
  • w:写
  • a:追加
  • t:文本文件
  • b:二进制文件
  • +:读和写
7.1.2 关闭文件

在文件使用完毕之后,需要使用fclose函数把文件关闭,用法fclose(文件指针)

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    FILE *fp = fopen("demo.txt", "r");
    if (fp == NULL)
    {
        printf("打开失败");
    }
    else
    {
        printf("打开成功");
    }
    // ...  操作文件
    // 关闭文件
    fclose(fp);
    return 0;
}

7.2 字符读写文件

字符读写文件主要用到了2个函数fgetcfputc,用法非常简单,当成api使用即可

7.2.1 fgetc读取文件

下面通过代码来实现读取当前目录下的demo.txt文件

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    FILE *fp = fopen("demo.txt", "rt");
    char ch;
    //如果文件不存在,给出提示并退出
    if (fp == NULL)
    {
        printf("打开失败");
    }
    //每次读取一个字节,直到读取完毕
    while ((ch = fgetc(fp)) != EOF)
    {
        putchar(ch);
    }
    putchar('\n'); //输出换行符
    fclose(fp);
    return 0;
}

输出结果

这正是我在demo.txt文件中写的内容,读取成功

重点:上面代码实现的关键是 while循环的条件(ch = fgetc(fp)) != EOFfgetc每次从位置指针所在的位置读取一个字符,保存到变量当中,然后指针后移一位,当到达文件末尾时,就无法读取字符了,于是返回EOF,over

这里也可以使用feof函数来判断是否到达文件结尾

7.2.2 fputc写入文件

写入文件采用fputc来实现,调用方式fputc(ch,fp)把字符ch写到文件指针变量fp所指向的文件中

代码语言:javascript
复制
#include <stdio.h>
int main()
{
    FILE *fp;
    char ch;
    //判断文件是否成功打开
    if ((fp = fopen("demo.txt", "wt+")) == NULL)
    {
        puts("打开失败");
    }
    printf("输入:\n");
    //每次从输入中读取一个字符并写入文件
    while ((ch = getchar()) != '\n')
    {
        fputc(ch, fp);//输出到文件内
    }
    fclose(fp);
    return 0;
}

7.3 字符串读写文件

在上面的字符读取文件方式中,一次只能读取一个字符,写也只能写一个,真的是超级慢,因此我们往往不会采用字符读取的方式

7.3.1 fgets读取文件

fgets() 函数用来从指定的文件中读取一个字符串,并保存到字符数组中,使用方法:

代码语言:javascript
复制
char *fgets (char *str, int n, FILE *fp);

str 为字符数组,n 为要读取的字符数目,fp 为文件指针

注意:由于字符串的结尾会自动添加\0,所以如果希望读取100个字符,n应当取101

注意:在n-1个字符之前如果出现了换行,或者到了文件的末尾,则读取结束,因此只能读取一行数据

示例:使用fgets读取文件

代码语言:javascript
复制
#include <stdio.h>
#include <stdlib.h>
#define N 100 // 宏定义N = 100
int main()
{
    FILE *fp = fopen("demo.txt", "rt");
    char str[N + 1];
    if (fp == NULL)
    {
        printf("打开失败");
    }
    while (fgets(str, N, fp) != NULL)
    {
        printf("%s", str);
    }
    fclose(fp);
    return 0;
}

输出结果

注意:fgets当遇到换行符时,也会一并读取,因此格式回和原文件相同,gets会忽略换行符

7.3.2 fputs写入文件

用法:int fputs(char *str,FILE *fp)

示例:向demo.txt文件中追加一句话

代码语言:javascript
复制
#include<stdio.h>
#include<string.h>
int main() {
    FILE *fp = fopen("demo.txt","at+");
    char attend[100];
    char str[102] = {0};
    if(fp == NULL) {
        printf("打开失败");
    }
    printf("输入一句话\n");
    gets(attend);
    strcat(str,"\n");
    strcat(str,attend);
    fputs(str,fp);
    fclose(fp);
    return 0;
}

输入输出结果

读写文件部分就写这么多了,还有几种方式没有写,可以自己了解一下

结语

C语言复习的全部内容就在这里了,博主也是肝了几天才整理完,关于内容上有什么问题或者错误欢迎留言指出,好好学习,天天向上,希望看到这里的你能取得好的成绩!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021/06/14 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 4. 函数
    • 4.1 定义一个函数
      • 4.1.1 定义一个无参函数
      • 4.1.2 无返回值函数
      • 4.1.3 定义有参函数
    • 4.2 调用函数
      • 4.3 形参和实参的区别
        • 4.4 函数的嵌套调用
          • 4.5 函数的递归调用
            • 4.6 全局变量和局部变量
              • 4.6.1 局部变量
              • 4.6.2 全局变量
              • 4.6.3 练习题
            • 4.7 内部函数和外部函数
              • 4.8 练习题
                • 第一题
                • 第二题
                • 第三题
            • 5. 指针
              • 5.1 指针变量
                • 5.1.1 使用指针变量
                • 5.1.2 定义指针变量
                • 5.1.3 引用指针变量
              • 5.2 指针变量作为函数参数
                • 5.3 通过数组引用指针
                  • 5.3.1 数组元素的指针
                  • 5.3.2 在引用数组元素时的运算
                  • 5.3.3 练习题
                • 5.4 指针数组和数组指针
                  • 5.5 通过指针引用字符串
                    • 5.6 指针作为函数返回值
                      • 5.7 练习题
                      • 6. 结构体
                        • 6.1 定义和使用结构体
                          • 6.1.1 建立结构体类型
                          • 6.1.2 定义结构体变量
                          • 6.1.3 读写结构体成员的值
                        • 6.2 结构体数组
                          • 6.3 结构体指针
                            • 6.3.1 获取结构体成员
                            • 6.3.2 结构体数组指针使用
                            • 6.3.3 结构体指针做函数参数
                          • 6.4 共用体类型
                            • 6.4.1 定义和使用共用体类型
                          • 6.5 枚举类型
                            • 6.6 typeof 声明新类型名
                              • 6.7 练习题
                              • 7. 文件的输入输出
                                • 7.1 打开和关闭文件
                                  • 7.1.1 打开文件
                                  • 7.1.2 关闭文件
                                • 7.2 字符读写文件
                                  • 7.2.1 fgetc读取文件
                                  • 7.2.2 fputc写入文件
                                • 7.3 字符串读写文件
                                  • 7.3.1 fgets读取文件
                                  • 7.3.2 fputs写入文件
                              • 结语
                              领券
                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档