头图

复习

0.1 定义函数

返回值类型   函数名称(形式参数类型1  形式参数名称1,形式参数类型2 形式参数名称2......)
{
     //函数内的语句
     return  返回值;
}

//获取两个整数中的较大值

int  GetMax(int a,int b)
{
      if(a>b)
      {
            return a;
      }
      else
      {
            return b;
      }
}

// 调用函数

int main()
{
     int  nMax = 0;
     nMax = GetMax(10+5,20);
     return 0;
}

形参:定义函数的时候,规定要传入的参数的类型
实参:调用函数的时候,传递的具体数据
形参的改变,不会影响实参的值。

和宏的比较
函数是先把参数的值给运算出来,然后传递给形参。 即便是最简单的函数,调用也会产生额外的消耗。
宏是一个预处理,是编译之前进行的一个替换,不宜编写的复杂。
什么时候使用函数,什么时候使用宏呢???
当代码比较简短,且大量调用的时候,使用宏
代码比较复杂,调用不频繁的时候,使用函数。

#include <stdio.h>
#define MUL(a,b) a*b


int GetMul(int m, int n)
{
    return m * n;
}
int main()
{
    int n1 = 0;
    int n2 = 0;
    n1 = MUL(5+1,10);//5+1*10
    printf("%d", n1);
    n2 = GetMul(5+1,10);
    printf("%d", n2);
    return 0;
}

0.2 全局变量和局部变量

全局变量:定义在函数外部的变量,可以被所有的函数所共享。
局部变量:定义在函数内部的变量,只在定义它的花括号内使用。
static:

a. 静态:修饰局部变量,离开静态局部变量的作用域时,不会被销毁,下次进入函数,依然能够使用上一次离开时的数据。
b. 隐藏:修饰全局变量或者函数,起到一个隐藏的作用,修饰之后,全局变量或者函数就只能在本文件中使用了

extern: (extern int a;)

a. 用于声明一个全局变量。遇到了extern,就意味着这个变量去别的地方寻找。

const:
a. 定义一个不能修改的常量。可以制定数据类型
const float pi = 3.14;
//#define PI (float)3.14

0.3 工程管理的方式

一般情况下,一个程序是由多个文件组成的。不同的文件中实现不同的函数。然后将他们组合到一起。
对于一个.cpp文件来说,要想将自己的函数提供给其他文件使用,需要实现一个对应的.h文件。
**.h文件存放函数和变量的声明。
.cpp文件存放函数的定义和全局变量的定义**。
在要使用函数和变量的文件中,包含对应的头文件就可以了。
**一定要注意的问题:
.h文件中,只能存放声明,千万不要放置定义。**

0.4 指针

基本使用:

a. 定义指针变量
b. 给指针变量赋值
c. 解引用

int* p =nullptr;
int a = 100;
p = &a;
*p = 400;

有一个基本的作用:
可以在函数的内部修改到外部的数据
不能说是形参改变的实参

1.结构体

1.1 结构体

#include <stdio.h>
int main()
{
    //假如我现在想要存储两个学生的信息
    //姓名  年龄  身高  体重
    char szName1[20] = { "xiaoming" };
    int nAge1 = 18;
    int nHeight1 = 176;
    double fWeight1 = 75.5;

    char szName2[20] = { "xiaohong" };
    int nAge2 = 18;
    int nHeight2 = 170;
    double fWeight2 = 60.5;
    //如果想要输出xiaoming的信息
    printf("姓名:%s 年龄:%d 身高:%d 体重:%lf",
        szName1, nAge1, nHeight1, fWeight1);
    //如果想要输出xiaohong的信息
    printf("姓名:%s 年龄:%d 身高:%d 体重:%lf",
        szName2, nAge2, nHeight2, fWeight2);
    //上面的语法,是可以解决问题的,但是比较麻烦
    //有一个更简单的方法,就是使用结构体
    struct 
    {
        char szName[20];
        int nAge ;
        int nHeight ;
        double fWeight;
    } stcStudent1 = {"xiaoming",18,176,75.5};
    //使用的时候,关键在于szName,nAge,nHeight
    //fWeight他们在语法上有了联系,他们的关系
    //就是由编译器自动维护了,不需要程序员维护了。
    //使用这种语法,我们仅仅需要记住stcStudent1名字
    //其他成员,通过"."就能够自动的找到了
    //减轻了我们管理这些数据的负担
    //这种优势,在数据越多的时候越明显。
    printf("姓名:%s 年龄:%d 身高:%d 体重:%lf",
        stcStudent1.szName,
        stcStudent1.nAge,
        stcStudent1.nHeight,
        stcStudent1.fWeight);


    return 0;
}

图片.png

1.2 结构体的一般定义方式

图片.png

#include <stdio.h>
int main()
{
    struct
    {
        char szName[20];
        int nAge;
        int nHeight;
        double fWeight;
    } stcStudent1 = { "xiaoming",18,176,75.5 };

    struct
    {
        char szName[20];
        int nAge;
        int nHeight;
        double fWeight;
    } stcStudent2 = { "xiaohong",18,170,65.5 };

    //一般情况结构体的类型名,都是大写
    struct STUDENT
    {
        char szName[20];
        int nAge;
        int nHeight;
        double fWeight;
    };

    struct STUDENT stcStudent3 = { "xiaobai",18,172,63.3 };
    struct STUDENT stcStudent4 = { "xiaolan",18,169,58.3 };
    //使用结构的成员的时候,成员是什么类型,就按照什么类型去使用
    printf("姓名:%s 年龄:%d 身高:%d 体重:%lf",
        stcStudent3.szName,
        stcStudent3.nAge,
        stcStudent3.nHeight,
        stcStudent3.fWeight);
    return 0;
}

1.3 关于结构体的内存布局

在内存中,结构体的成员,是按照顺序,依次排列的。
结构体的大小是所有成员的大小之和。因为对齐的原因,大小会稍微大一些。这里的4个CC就是对齐的结果。
图片.png

1.4 结构体的嵌套

一个结构体的成员是另外一个结构体类型

#include <stdio.h>

struct NOTEBOOK
{
    int nId;      //序列号
    double fPrice;//价格
    int nType;    //品牌
    //.....
};
struct STUDENT
{
    char szName[20];
    int nAge;
    int nHeight;
    double fWeight;
    NOTEBOOK stcNoteBook;
};
int main()
{
    STUDENT stcStudent1 = {
        "xiaoming",18,176,75.5,{100,5000.5,2}
    };
    //访问结构体成员的时候,可以一级一级的往下访问
    printf("%d %lf", 
        stcStudent1.stcNoteBook.nId,
        stcStudent1.stcNoteBook.fPrice);
    stcStudent1.nAge = 20;
    stcStudent1.stcNoteBook.nId = 200;
    stcStudent1.szName[0] = 'y';
    return 0;
}

1.5 结构体数组

当我们需要定义很多结构体变量的时候,我们也可以使用结构体数组

#include <stdio.h>

struct NOTEBOOK
{
    int nId;      //序列号
    double fPrice;//价格
    int nType;    //品牌
    //.....
};
struct STUDENT
{
    char szName[20];
    int nAge;
    int nHeight;
    double fWeight;
    NOTEBOOK stcNoteBook;
};
int main()
{
    //假如此时有5个学生,挨个定义会比较麻烦,可以使用结构体数组
    //初始化的时候,没有填写的字段,自动设置为0
    struct STUDENT stcTest[5] = {
        {"xiaoming",18,176,75.5,{100,5000.5,2}},
        {"xiaohong",18,175,76.5,{200,8000.5,3}},
        {"xiaobai"}
    };
    //如果要访问
    printf("%s", stcTest[1].szName);
    printf("%d\n", stcTest[1].nAge);

    for (int i = 0; i < 5; i++)
    {
        printf("%s %d\n", stcTest[i].szName, stcTest[i].nAge);
    }

    return 0;
}

2.联合体

联合体的语法和结构体语法几乎是一摸一样的。关键字不一样。
他们有什么区别???
结构体和联合体最大的区别在于:
联合体的所有成员(字段),都是共享内存空间的。联合体的大小,就是最大的成员的大小。
结构体的每一个成员(字段)都是单独占用内存空间的。

#include <stdio.h>
struct TEST1
{
    char cCh;
    int nNum;
};

union TEST2
{
    char cCh;
    int nNum;
};


int main()
{
    struct TEST1 stcTest1 = {0};
    printf("%c %d\n", stcTest1.cCh, stcTest1.nNum);
    stcTest1.cCh = 'A';
    printf("%c %d\n", stcTest1.cCh, stcTest1.nNum);
    stcTest1.nNum = 100;
    printf("%c %d\n", stcTest1.cCh, stcTest1.nNum);
    union TEST2 stcTest2 = { 0 };
    printf("%c %d\n", stcTest2.cCh, stcTest2.nNum);
    stcTest2.cCh = 'A';
    printf("%c %d\n", stcTest2.cCh, stcTest2.nNum);
    stcTest2.nNum = 100;
    printf("%c %d\n", stcTest2.cCh, stcTest2.nNum);


    return 0;
}

图片.png

3.类型定义

typedef 是能够给数据类型起一个新的名字(别名)
图片.png
图片.png
图片.png

**typedef 和结构体合在一起的用法:
图片.png

#include <stdio.h>

typedef struct _TEST1
{
    char cCh;
    int nNum;
}TEST1;
struct _TEST2
{
    char cCh;
    int nNum;
}TEST2;
int main()
{
    //使用typedef就可以在定义变量的时候,少写一个 struct
    TEST1 stc = { 'A',100 };

    struct _TEST2 stc1 = { 'A',100 };
    return 0;
}

4.堆空间

4.1 基本用法

申请空间:malloc
释放空间:free

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //局部变量所在的空间,称之为 【栈空间】
    //栈空间 是自动申请,自动释放的,大小都是编译器确定的
    //这个变量,占用空间,固定的就是4个字节
    int a = 0;
    //这个数组,占用空间,固定的就是40个字节
    int arr[10] = {0};
    int n = 0;

    printf("告诉我,你需要多少个int:");
    scanf_s("%d", &n);
    //malloc申请堆空间,
    //它会将申请到的堆空间的地址,作为返回值
    //返回出来,那么此时,存储地址,应该是指针
    //一般申请堆空间,就应该配合指针一起使用
    int* p1 = (int*)malloc(n*sizeof(int));

    //这里申请4个字节
    int* p2 = (int*)malloc(1 * sizeof(int));

    //当我们不想使用堆空间了,需要自己主动去释放
    free(p1);
    p1 = nullptr;
    free(p2);
    p2 = nullptr;
    return 0;
}

4.2 常见用法

#include <stdio.h>
#include <stdlib.h>
int main()
{
    int n = 0;
    int* p1 = nullptr;
    printf("告诉我,你需要存储多少个人的年龄:");
    //scanf_s("%d", &n);
    ////int arr[n] = { 0 }; 数组长度必须是常量
    //int* p1 = (int*)malloc(n * sizeof(int));

    //for (int i = 0; i < n; i++)
    //{
    //    printf("请输入一个年龄:");
    //    scanf_s("%d",&p1[i]);
    //}
    int arr[10] = { 4,2,1,4,6,8,0,7,8 };
    p1 = arr;
    for (int i = 0; i < 10; i++)
    {
        printf("%d", p1[i]);
    }
    return 0;
}

4.3 数组和堆的区别

1.数组的长度是常量,堆在申请的时候,既可以是常量,也可以是变量。
2.堆需要我们自己释放,数组不需要我们自己释放,自动释放的

#include <stdio.h>
#include <stdlib.h>
int main()
{
    //1. 数组的长度是常量,堆在申请的时候,既可以是常量,也可以是变量。
    int n = 0;
    int* p1 = nullptr;
    //printf("告诉我,你需要存储多少个人的年龄:");
    //scanf_s("%d", &n);
    //int arr[n] = { 0 }; //数组长度必须是常量
     p1 = (int*)malloc(n * sizeof(int));
    //2. 堆需要我们自己释放,数组不需要
    //while (true)
    //{
    //    malloc(2);
    //}
    while (true)
    {
        int nArr[10];
    }
    return 0;

4.4 两个内存操作函数

memcpy 内存拷贝
memset 给一块内存,每个自己都设置为相同的数据。

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
int main()
{
    int* p1 = nullptr;
    p1 = (int*)malloc(10 * sizeof(int));
    memset(
        p1,             //起始地址
        0,              //要设置的初始值 
        10 * sizeof(int)// 一共设置多少个字节
    );
    p1[0] = 100;
    p1[1] = 200;
    int* p2 = nullptr;
    p2 = (int*)malloc(10 * sizeof(int));

    memcpy(
        p2,
        p1,
        10 * sizeof(int)
    );
  free(p1);
  p1 = nullptr;
  free(p2);
  p2 = nullptr;
    return 0;
}

4.5 一些概念

内存泄漏:假如内存申请了,但是忘记释放了,此时就称之为内存泄漏,内存会被一直占用到释放或者程序结束。
悬空指针:一个指针被释放了,但是没有被置为null,指针就还会指向这个被释放的区域,假如无意使用到了,就会崩溃。
free(p1)
//....
*p1 = 100;
野指针:没有被初始化的指针
int * p2;
*p2 = 100;
运行的程序,内存区域的划分:
1.堆区 动态生存期 申请就存在 释放就销毁

a. malloc申请的内存,都在堆区

2.栈区 自动生存期 进入作用域 自动申请 离开作用域 自动销毁

a. 局部变量,还有参数,都是在栈区

3.全局数据区 静态生存期 程序开始运行就申请 程序结束 才销毁

a. 全局变量,静态局部变量  都在全局数据区

4.常量区 静态生存期 程序开始运行就申请 程序结束 才销毁

a. 字符串都在常量区

5.代码区 静态生存期 程序开始运行就申请 程序结束 才销毁

a. 程序的指令,都在代码区

图片.png

4.6内存分区详细

补充资料阅读请到:
C语言内存分区-(堆,栈,全局/静态存储区,自由存储区,代码区)与可执行程序的三段-(Text段,Date段,Bss段)

一道练习题,回顾今天和昨天的知识(题目名称:设计 Goal 解析器)

请你设计一个可以解释字符串 command 的 Goal 解析器 。command 由 "G"、"()" 和/或 "(al)" 按某种顺序组成。Goal 解析器会将 "G" 解释为字符串 "G"、"()" 解释为字符串 "o" ,"(al)" 解释为字符串 "al" 。然后,按原顺序将经解释得到的字符串连接成一个字符串。

给你字符串 command ,返回 Goal 解析器 对 command 的解释结果。

 

示例 1:

输入:command = "G()(al)"
输出:"Goal"
解释:Goal 解析器解释命令的步骤如下所示:
G -> G
() -> o
(al) -> al
最后连接得到的结果是 "Goal"

示例 2:

输入:command = "G()()()()(al)"
输出:"Gooooal"

示例 3:

输入:command = "(al)G(al)()()G"
输出:"alGalooG"

char * interpret(char * command) {
    int len = strlen(command);
    char *res = (char *)malloc(sizeof(char) * (len + 1));
    int pos = 0;
    for (int i = 0; i < len; i++) {
        if (command[i] == 'G') {
            pos += sprintf(res + pos, "%s", "G");
        } else if (command[i] == '(') {
            if (command[i + 1] == ')') {
                pos += sprintf(res + pos, "%s", "o");
            } else {
                pos += sprintf(res + pos, "%s", "al");
            }
        }
    }
    return res;
}


瞿小凯
1.3k 声望593 粉丝