C语言复习

2023/10/5

一、初识c语言:


单位:

bit(0/1)->8bit=1byte->1024byte=1kb->1024kb->1mb


常量:

const int num = 100; arr[const]是错误的,enum枚举常量


字符串:

在计算字符串长度的时候\0是结束标志,不算作字符串内容

char ch1[] = {'a','b','c'};

char ch2[] = "abc";自动加上/0

printf("%s\n",ch1);//打印结果为abc乱码

printf("%s\n",ch2);//打印结果为abc


转义字符:

\62被解析成一个转义字符           printf("%d\n",strlen("c:\test\628\test.c"));


Printf输出 

关于printf格式化输出

%d整型输出,%ld长整型输出,

%i和%d都是表示有符号十进制整数,但%i可以自动将输入的八进制(或者十六进制)转换为十进制,而%d则不会进行转换。

%o以八进制数形式输出整数,

       %x以十六进制数形式输出整数,

%X%x是输出十六进制数字,%X是输出的十以上的字母大写

%u以十进制数输出unsigned型数据(无符号数)。

Un的最大值没有符号位的限制,而int有符号位限制只能到2147483647

%c用来输出一个字符,

%s用来输出一个字符串,

%f用来输出实数,以小数形式输出,

%e以指数形式输出实数,

%g根据大小自动选f格式或e格式,且不输出无意义的零。


选择语句:

if()

        printf("1");

else if()

        printf("2")

else

        printf("3")

        


数组:

初始化:

int arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };//定义一个整形数组,最多放十个元素
int arr2[10] = { 1,2,3,4,5 };//不完全初始化,剩余的默认初始化为0
char arr3[10] = { a,b,c,d };//不完全初始化,剩余的默认初始化为/0

char才会补上/0

下标:


运算符:

算数操作符

+        -        *        /        %

移位操作符

>>        <<

位操作符

&        ^        |

赋值操作符

=        +=        *=        /=        %=        ^=        |=        >>=        <<=

单目操作符


!        逻辑反操作

-         负值

+        正值

&        取地址

sizeof        操作符的类型长度(以字节为单位)

~        对一个数的二进制位按位取反

++        前置、后置++

--          前置、后置--

*          间接访问操作符(解引用操作符)

(类型)        强制类型转换

关系操作符

>

>=

<

<=

!=        用于测试"不相等"

==       用于测试"相等"

逻辑操作符

&&        逻辑与

||           逻辑或

条件操作符

exp1 ? exp2 : exp3

逗号表达式

exp1,exp2,exp3, ...expN

下标引用 函数调用和结构成员

[]        ()        .        ->


关键字

   break        case        char        const       continue        default         do        double        else        enum        extern        float        for        goto        if        int        long        register        return        short        signed        sizeof     struct        switch        typedef        union        unsigned   


关键字 typedef

typedef 顾名思义是类型定义,这里应该理解为类型重命名。

//将unsigned int 重命名为uint_32,所以uint_32也是一个类型名
typedef unsigned int uint_32;
 
int main()
{
    //num1和num2的类型是一样的
    unsigned int num1 = 0;
    uint_32 num2 =0;
    return 0;
}
uint_32相当于是unsigned int的别名。


关键字 static

修饰局部变量

第一种情况:

第一次调用函数开始时a被创建,a的生命周期开始,调用函数结束a被销毁,第二次调用函数a再次被创建,每次a被创建时都是0,每次都++,所以每次都打印1。

第二种情况:

第一次调用函数开始a被创建,a的生命周期开始,调用函数结束a仍然保留,第二次调用函数时a为1,再次++,所以打印结果为1-10。

被static之后的变量会被放在静态区

static的修饰改变的局部变量的存储位置,因为存储位置的差异,使得执行效果不一样。


修饰全局变量:

//add.c
int g_val = 2022;
//test.c
extern int g_val;//声明外部符号
int main()
{
    printf("%d\n",g_val);
    return 0;
}
我们可以看到,在add.c文件中定义的变量,在test.c文件中可以使用

这是因为全局变量本身是具有外部链接属性的

test.c文件通过【extern】使用了在add.c文件中定义的变量

但是如果全局变量被static修饰,这个外部链接属性就变成了内部链接属性,这个全局变量只能在自己所在的源文件内部使用,比如:

//add.c
static int g_val = 2022;
//test.c
extern int g_val;//报错:无法解析的外部符号 g_val
int main()
{
    printf("%d\n",g_val);
    return 0;
}
static的修饰,会把外部链接属性变成内部链接属性,最终使得全局变量的作用域变小了。

当你想要“独享”一个全局变量时,就可以用static修饰该全局变量。


修饰函数:

//add.c

static int Add(int x,int y)

{

        return x + y;

}

//test.c

        extern int Add(int,int);                //报错:无法解析的外部符号 Add

函数本身是具有外部链接属性的

被static修饰后,外部链接属性就变成了内部链接属性


栈区、堆区、静态区

当我们使用计算机内存时,一般会将计算机的内存分为三个区域----堆区、栈区、静态区。
栈区--------用于存放局部变量、函数的参数,总之,栈区里放的都是相对临时的变量。
堆区--------动态内存分配,malloc、calloc、realloc、free所申请空间都是在堆区申请的。
静态区--------用于存放静态变量和全局变量
栈区里存放的变量、数据进入作用域创建,出了作用域销毁,是临时变量
静态区存放的变量、数据创建后直到程序结束才销毁。


指针:

指针

1.指针就是地址

2.口头语说指针一般指的是指针变量


内存:

每个内存单元的大小是1个字节(byte),给内存单元进行了编号,这些编号就被称为该内存单元的地址(指针),如果访问一个内存单元,那内存单元的地址(指针),如何产生呢?

32位机器上有32根地址线,地址线如果通电,电信号则由低电频转为高电频(0变成1)

00000000000000000000000000000000

00000000000000000000000000000001

每根地址线对应一个内存单元,32位机器最多能产生2^32地址线(地址),就可以管理2^32个内存单元,也就是2^32字节(byte)的内存空间。

2^32字节=4GB,所以32位机器最多能访问4GB的内存空间。

地址采用2进制表达太复杂,所以我们通常用16进制数字来表达地址

2进制:0~1

8进制:0~8

10进制:0~9

16进制:0~9 a b c d e f

int a = 1

0x0012ff40        01

0x0012ff41        00

0x0012ff42        00

0x0012ff43        00

打印出来的是a所占内存空间中四个字节的第一个字节的地址(最小的地址)。


指针的作用:

int * pa = &a;//将a的地址存储到指针变量pa中

告诉我们pa是指针变量,而前面的int告诉我们pa指向的是int类型的变量


指针的大小

32位机器上 是32个bit位

32位的机器上,指针变量存放的是32bit的地址

32位机器上,指针变量大小是4个字节

同理,64位机器上,指针变量大小是8个字节


结构体


struct Stu
{
    char name[20];//名字
    int age;//年龄
    char sex[5];//性别
    char id[20];//学号
}
结构体的初始化:

    //引入结构体变量
    struct Stu s = { "菠萝",18,"男","123456X" };
    //打印结构体信息
    printf("%s %d %s %s\n", s.name, s.age, s.sex, s.id);
 
    //另一种打印方法(->操作符)
    struct Stu* ps = &s;
    printf("%s %d %s %s\n", ps->name, ps->age, ps->sex, ps->id);






二、分支与循环

C语言是一门结构化的程序设计语言

C语言支持三种结构:

1.顺序结构

2.选择结构

3.循环结构

控制语句用于控制程序的执行流程,以事先程序的各种结构方式(C语言支持三种结构:顺序结构、选择结构、循环结构),它们由特定的语句定义符组成,C语言有九种控制语句。

1.条件判断语句也叫分支语句:if语句、switch语句;

2.循环执行语句:do while语句、while语句、for语句;

3.转向语句:break语句、goto语句、continue语句、return语句。


1.条件判断语句

if语句


if(表达式1)
    语句1;
else if(表达式2)
    语句2;
else
    语句3;

代码风格很重要       else的匹配:else是和它最近的if匹配的。

防止少写一个=


Switch
  1. switch语句后的括号里只能放整形表达式!!case后必须是整形常量表达式!!
  2. switch语句中的case只是决定入口,并不能决定出口。
  3. 只需要在每个语句后面加上break;


2.循环语句:

  • while

  • for

  • do while


while

break在while循环中的作用:

其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。

while中的break是用于永久终止循环的。

continue在while循环中的作用就是:

continue是用于终止本次循环的,也就是本次循环中continue后面的代码不会再执行,

而是直接跳转到while语句的判断部分。进入下一次的循环入口判断。

while (i<=10)

{

if(i == 5)

continue(死循环);/break(1234);

printf("%d ",i);

i = i+1;

}


for循环

建议:

1.不可在for循环体内修改循环变量,防止for循环失去控制。

2.建议for语句的控制循环变量的取值采用"前闭后开区间"写法

一些for循环的变种:

笔试题

判断部分k=0的返回值为0,所以不成立,退出循环


do...while()循环

先执行在判断,循环至少执行一次

do

循环语句;

while(表达式);

常用于玩游戏


goto语句

一次跳出两层或多层循环

多层循环这种情况使用break是达不到目的的。它只能从最内层循环退出到上一层的循环。


三、函数:


函数的调用:

  1. 函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参。
  2. 传址调用是把函数外部创建变量的内存地址传递给函数参数的一种调用函数的方式,这种传参方式可以让函数和函数外边的变量建立起真正的联系,也就是函数内部可以直接操作函数外部的变量。

嵌套调用:

printf("%d", printf("%d", printf("%d", 43)));

结果4321

函数的声明和定义

函数定义


头文件add.h的内容

放置函数的声明

//函数的声明

int Add(int x, int y);


源文件add.c的内容

放置函数的实现

//函数Add的实现

int Add(int x, int y)

{

return x + y;

}


函数递归

程序调用自身的编程技巧称为递归。

递归的两个必要条件

  • 存在限制条件,当满足这个限制条件的时候,递归便不再继续。
  • 每次递归调用之后越来越接近这个限制条件。

各种练习:

红色代表递蓝色代表归。


#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int test(char* pa)
{
    if (*pa != '\0')
        return 1 + test(pa + 1);
    else
        return 0;
}

int main()
{
    char a[100] = { 0 };
    gets(a);
    printf("%d",test(a));
    return 0;
}

利用递归来求第n个斐波那契数运算量是特别大的,效率也是比较低的。

int Fib(int n)

{

if (n > 2)

return Fib(n - 1) + Fib(n - 2);

else

return 1;


汉诺塔:

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<math.h>
int flag = 0;
void print(char x, char y)
{
    printf("从%c移动到%c\n", x, y);
}

int Hanoi(int n, char a, char b, char c)
{
    if (n == 1)
    {
        print(a, c);
        flag++;
    }
    else
    {
        Hanoi(n - 1, a, c, b);  //第一步,把a上面的n-1个借助c移动到b
        print(a, c);            //第二步,把a最底下的那个移动到c,此时a空了
        flag++;
        Hanoi(n - 1, b, a, c);  //第三步,把b上面的n-1个通过a移动到c,结束
    }
    return pow(2.0, (double)n)-1;//这个是数学规律2^n-1,而flag是一步一步累加的
}
int main()
{
    int n = 0;
    scanf("%d", &n);
    printf("%d\n%d", Hanoi(n, 'A', 'B', 'C'),flag);
    return;
}



函数栈帧:函数栈帧详解_函数调用栈帧过程(带图详解)-CSDN博客

函数栈帧(stack frame)就是函数调用过程中在程序的调用栈(call stack)所开辟的空间,这些空间是用来存放:

  • 函数参数和函数返回值
  • 临时变量(包括函数的非静态局部变量以及编译器自动生成的其他临时变量)
  • 保存上下文信息(包括在函数调用前后需保持不变的寄存器)

只要理解了函数栈帧的创建和销毁,以下问题就能够很好的理解了:

  • 局部变量是如何创建的?
  • 为什么局部变量不初始化的话内容是随机的?
  • 函数调用时参数是如何传递的?传参的顺序是怎么样的?
  • 函数的形参和实参分别是怎样实例化的?
  • 函数的返回值是如何带回的?

函数栈帧的创建和销毁:

  1. 先入栈的数据后出栈(First In Last Out,FIFO)
  2. 在计算机系统中,栈则是一个具有以上属性的动态内存区域。程序可以将数据压入栈中,也可以将数据从栈顶弹出。压栈操作使得栈增大,而弹出操作使得栈减小。
  3. 在经典的操作系统中,栈总是向下增长(由高地址向低地址)的。
  4. 栈顶由成为esp的寄存器进行定位的,栈底是由称为ebp的寄存器进行定位的。


相关寄存器和汇编指令

相关寄存器

eax:通用寄存器,保留临时数据,常用于返回值

ebx:通用寄存器,保留临时数据

ebp栈底寄存器,维护函数栈帧

esp栈顶寄存器,维护函数栈帧

eip:指令寄存器,保存当前指令的下一条指令的地址

相关汇编命令

mov数据转移指令

push数据入栈,同时esp栈顶寄存器也要发生改变

pop数据弹出至指定位置,同时esp栈顶寄存器也要发生改变

sub:减法命令

add:加法命令

call:函数调用,1.压入返回地址 2.转入目标函数

jmp通过修改eip转入目标函数,进行调用

ret:恢复返回地址,弹出eip,类似pop eip命令,回到进入函数前的下一条指令

rep stos:重发上一个命令ecx次,将ebp-0x2h到ebp的内容全部改成0xcc cc cc cc

main函数调用时,在栈区开辟的空间的其中每一个字节都被初始化为0xCC,而arr数组是一个未初始化的数组,恰好在这块空间上创建的,0xCCCC(两个连续排列的0xCC)的汉字编码就是“烫”,所以0xCCCC被当作文本就是“烫”。

现在main函数的函数栈帧就创建完毕



参考:函数调用栈帧过程(带图详解)_函数调用栈帧过程(带图详解)-CSDN博客

码农菠萝_函数,数组,初识C语言-CSDN博客

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值