飞起来的C语言

定义符号常量:

格式:#define     标识符(大写)      常量 (小写)         把所有标识符替换成常量

转义字符:

\0表示字符串到此为止   \b表示退格的意思相当于backspace    %d整形   %f 浮点型   %p  打印地址类型   %s 字符类型

运算符:

c语言通过提供运算符来支持我们对数据进行处理

表达式:

用运算符和括号将操作数连接起来的式子

sizeod运算符:

sizeof运算符用于获得数据类型或表达式的长度

-sizeof(object);           sizeof(对象);

-sizeof(type_name);    sizeof(类型);

-sizeof object;               sizeof  对象;

signed 和 unsigned :

[signed]       int

 unsigned     int

类型限定符、用于限定char类型或者任何整形变量的取值范围、

signed 带符号位的(可以存放负号数)、unsigned不带符号位(只能存放0和正数)

符号位:

存放signed类型的存储单元中、左边第一位表示符号位。如果该位为0、表示整数为正、如果该位为 1 表示整数为负数。

int result = 2;   printf("result = %d\n",result);

unsigned int result = 2;   printf("result = %u\n",result);

一个 32 位的整型变量、除去左边第一位符号位、剩下的表示值的只有 31 个比特位。

事实上计算机是用补码的形式来存放整数的值。

补码:

正数的补码是该数的二进制形式。

负数的补码需要通过以下几步获得:

1.先取得该数的绝对值的二进制形式

2.再将第一步的值按位取反

3.最后将第二步的值加1

比特位:CPU 能读懂的最小单位——比特位、bit  、b

字    节: 内存机构的最小寻址单位 —— 字节 、Byte 、B

1Byte == 8bit

1个字节可以表示多大的数  :  11111111    十进制为   255   十六进制为  FF

二进制转换十进制的算法:2 的 n 次方减 1

关系运算符:

优先级相同(高):<        <=        >        >=

优先级相同(底):==      !=                (=是赋值     ==是判断两数是否相等)

逻辑运算符:(返回逻辑值:真1    假0)

!         逻辑非       优先级:高        !a          若 a 为真、!a为假

&&     逻辑与       优先级:中         a&&b      只有 a b 同时为真、结果才为真、否则为假

||         逻辑或       优先级:低         a||b         a b 只要有一个为真、结果为真、否则为假

短路求值:

短路求值又称最小化求值、是一种逻辑运算符的求值策略。只有当第一个操作数的值无法确定逻辑运算的结果时、才对第二个操作数进行求值

C语言对于逻辑与和逻辑或采用短路求值的方式

过滤回车:写程序时、有时在控制台输入后 、敲的回车会被程序接收、此时需要过滤掉    getchar();

#include<stdio.h>

int main()

{

    int count = 0;                            //初始化计数器

    printf("请输入英文字符");

    while (getchar()!='\n')               //循环条件

    {

        count=count+1;                     //更新计数器

    }

    printf("你共输入了%d个字符\n",count);

    return 0;

}

for(循环初始化;循环条件;循环调整){

      循环体;

}

goto语句:

语法:  goto 标签;跳转到标签的位置      在开发过程中应避免使用、会破坏原有代码的逻辑

字符串处理函数:

获取字符串的长度:strlen

拷贝字符串:   strcpy 和 strncpy

链接字符串:    strcat  和  strncat

比较字符串:   strcmp   和   strncmp

避免访问未初始化的指针

数组名其实是数组第一个元素的地址

*(arry+i)==arry[i]         *(*(array+1)+3)=array[1][3]

数组指针和二维数组:

定义一个数组指针:int (*p)[3];    p是指向三个元素的数组指针

int (*p)[3]=array;

void指针:

void 是无类型的、但是void指针被称为通用指针、就是可以指向任意类型的数据。即任何类型的指针都可以赋值给void指针

Null指针:

int *p;    定义指针却不给它赋值的话、默认值是随机的、被称为、迷途指针、野指针、悬空指针。程序出错时不好排查

当不清楚要将指针初始化为什么地址时,可以初始化为NULL、在对指针进行解引用时、先检索该指针是否为NULL、为大型程序节约时间

形参和实参:

形参:形式参数    并没有传递确定的值、只是个占位符     只在函数内部有效

实参:实际参数     调用时传递了确定的值     调用时实参会传给形参   单向的

可变参数:

#include <stdarg.h>

-va_list

-va_start

-va_arg

-va_end

#include<stdio.h>
#include<stdarg.h>
int sum(int n, ...); //指定后面有多少个参数  ...作为占位符 表示参数数目不确定
int sum(int n,...)//n为3表示后面有三个参数   求所有参数的和
{
    int i, sum = 0;
    va_list vap;//定义参数列表  其实是定义一个字符指针的类型
    va_start(vap, n);//初始化参数列表   对字符指针进行一个计算
    for (i = 0; i < n;i++)
    {
        sum += va_arg(vap,int);//获取每个参数的值
    }
    va_end(vap);//关闭参数列表
    return sum;
}
int main()
{
    int result;
    result = sum(3, 1, 2, 3);
    printf("result1=%d\n", result);

    result = sum(3, 11, 2, 3);
    printf("result2=%d\n", result);

    result = sum(3, 1, 22, 3);
    printf("result3=%d\n", result);
    return 0;
}

传值和传址:

每个函数的内部都是互相独立的、它们的变量只在函数内部生效、不同函数之间是不能访问对方的变量的(指针可以解决)

传数组其实是传的地址

 

指针函数:

函数的类型实际上指的就是函数的返回值

使用指针变量作为函数的返回值、就是指针函数

#include<stdio.h>
char *getword(char);
char *getword(char c)
{
    switch (c)
    {
        case 'A':return "Apple";
        case 'B':return "Banana";
        case 'C':return "Cat";
        case 'D':return "Dog";
        default:return "None";
    }
}
int main()
{
    char input;
    printf("请输入一个字母:");
    scanf("%c", &input);
    printf("%s\n", getword(input));//getword获得的是一个字符串   通常没有类型定义字符串、都是用char类型的指针来定义  
    return 0;                      //char类型的指针实际上是指向一个字符、我们用它来指向字符串的第一个字符  截止于空字符\0
}

 注意:不要返回局部变量的指针    局部变量:在函数里定义的变量作用范围仅限于函数内部:

#include<stdio.h>
char *getword(char);
char *getword(char c)
{
    char str1[]="Apple";
    char str2[]="Banana";
    char str3[]="Cat";
    char str4[]="Dog";
    char str5[]="None";
    switch (c)
    {
        case 'A':return str1;
        case 'B':return str2;
        case 'C':return str3;
        case 'D':return str4;
        default:return str5;
    }
}
int main()
{
    char input;
    printf("请输入一个字母:");
    scanf("%c", &input);
    printf("%s\n", getword(input));//getword获得的是一个字符串   通常没有类型定义字符串、都是用char类型的指针来定义  
    return 0;                      //char类型的指针实际上是指向一个字符、我们用它来指向字符串的第一个字符  截止于空字符\0
}

程序也能运行但是输入没有输出    警告信息:返回的是局部变量的一个地址

 函数指针:

指针函数  ->int *p();

函数指针  ->int (*p)();

#include<stdio.h>
int square(int);
int square(int num)
{
    return num * num;
}
int main()
{
    int num;
    int (*fp)(int);
    printf("请输入一个整数:");
    scanf("%d", &num);
    fp = square;//与int square(int num)函数是等价的、得到此函数的地址
    printf("%d*%d=%d\n", num, num, (*fp)(num));
    return 0;
}
#include<stdio.h>
int add(int, int);
int sub(int, int);
int calc(int (*fp)(int, int), int, int);
int add(int num1,int num2)
{
    return num1 + num2;
}
int sub(int num1,int num2)
{
    return num1 - num2;
}
int calc(int (*fp)(int, int), int num1, int num2)//int (*fp)(int, int)是指针、指向一个函数
{
    return (*fp)(num1, num2);
}
int main()
{
    printf("3+5=%d\n", calc(add, 3, 5));
    printf("3-5=%d\n", calc(sub, 3, 5));
    return 0;
}

函数指针作为返回值

若一个函数为select、本身有两个参数、返回返回值是一个函数指针、这函数指针也有两个参数、并且其返回值为整型

#include<stdio.h>
int add(int, int);
int sub(int, int);
int calc(int (*)(int, int), int, int);
int (*select(char))(int, int);//select后面是一个参数列表、可以确定select是一个函数 参数是char类型,返回的是一个指针、返回整型并且带有两个参数的函数指针
int add(int num1,int num2)
{
    return num1 + num2;
}
int sub(int num1,int num2)
{
    return num1 - num2;
}
int calc(int(*fp)(int,int),int num1,int num2)
{
    return (*fp)(num1, num2);
}
int (*select(char op))(int,int)
{
    switch (op)
    {
        case '+':return add;
        case '-':return sub;
    }
}
int main()
{
    int num1, num2;
    char op;
    int (*fp)(int, int);
    printf("请输入一个式子(如1+3):");
    scanf("%d%c%d", &num1, &op, &num2);
    fp = select(op);
    printf("%d%c%d=%d\n", num1, op, num2, calc(fp, num1, num2));
}

extern关键字      一个函数的全局变量在函数定义之后定义

用extern关键字告诉编译器、这个变量我在后面定义了、你先别报错

不要大量使用全局变量

使用全局变量会使你的程序占用更多的内存,因为全局变量从被定义时候开始,直到程序退出才被释放。
污染命名空间,虽然局部变量会屏蔽全局变量,但这样一来也会降低程序的可读性,人们往往很难一下子判断出每个变量的含义和作用范围。

提高了程序的耦合性,牵一发而动全身,时间久了,代码长了,你都不知道全局变量被哪些函数修改过。
作用域

变量被定义在程序的不同位置时、其作用范围不一样。这个作用范围就是作用域。

C语言编译器可以确认4种不同类型的作用域

——代码块作用域      :

    (block scope) 在代码块中定义的量、具有代码块作用域。作用范围从变量定义开始、到标志该代码块结束的右大括号(})结束。

    尽管函数的形式参数不在大括号内定义、但其同样具有代码块作用域、隶属于包含函数体的代码块。

——文件作用域          :

    (file scope) 任何在代码块之外声明的标识符都具有文件作用域、作用范围从它们的声明位置开始、到文件的结尾处都是可以访问的。

    另外、函数名也具有文件作用域、因为函数名本身也是在代码块之外。

——原型作用域           :

     (prototype scope)  原型作用域只适用于那些在函数原型中声明的参数名。函数在声明的时候可以不写参数的名字(但参数类型是必须要写上的)、其实函数原型的参数名还可以随便写一个名字、不必与形式参数相匹配(这样无意义)

——函数作用域            :

    (function scope) 函数作用域只适用于goto语句的标签、作用将goto语句的标签限制在同一个函数内部、以防止出现重名标签。

定义和声明

定义:当一个变量被定义的时候、编译器为变量申请内存空间并填充一些值。

声明:当一个变量被声明的时候、编译器就知道该变量被定义在其他地方。

            声明是通知编译器该变量名及相关的类型已存在、不需要再为此申请内存空间。

局部变量既是定义又是声明。定义只能来一次、否则就叫做重复定义某个同名变量、而声明可以有很多次。

 

链接属性

编译器把源文件变成可执行文件需要两个步骤:编译、链接

编译:将源代码生成机器代码也就是目标文件

链接:将目标了文件及相关的库文件合并得到可执行文件

链接属性三种:

external(外部的):多个文件声明的同名标识符表示同一个实体

internal(内部的):单个文件中声明的同名标识符表示同一个实体

none(无):声明的同名标识符被当作独立不同的实体

只有具备文件作用域的标识符才能拥有external或internal的链接属性、其他作用域的标识符都是none属性

默认情况下、具备文件作用域的标识符拥有external属性。也就是说该标识符允许跨文件访问。对于external属性的标识符、无论在不同文价中声明多少次、表示的都是同一个实体。

使用static关键字可以使原先拥有external属性的标识符变为internal属性。这里有两点需要注意:

1、只对具有文件作用域的标识符生效(对于拥有其他作用域的标识符是另一种功能)

2、链接属性只能修改一次、也就是说一旦将标识符的链接属性变为internal、就无法变回external了

生存期

C语言的变量拥有两种生存期

1、静态存储期

2、自动存储期

具有文件作用域的变量隶属于静态存储期、函数也属于静态存储期。属于静态存储期的变量在程序执行期间将一直占据存储空间、直到程序关闭才释放。

具有代码块作用域的变量一般情况下属于自动存储期。属于自动存储期的变量在代码块结束时将自动释放存储空间。

存储类型

C语言提供了5种不同的存储类型

——auto     :(自动变量 ) 在代码块中声明的变量默认的存储类型类型就是自动变量、使用关键字auto来描述

——register:(寄存器变量)将一个变量声明为寄存器变量、那么该变量就有可能被存放于CPU的寄存器中。为啥是有可能:因为CPU内存有限、不可能把所有声明的变量都放进去

                          当将变量声明为寄存器变量、就不能通过取地址运算符获得该变量的地址

——static     :使用static来声明局部变量、那么就可以将局部变量指定为静态局部变量。

                         static使得局部变量具有静态存储期、所以它的生存期与全局变量一样、直到程序结束后才释放

——extern    :告诉编译器这个变量在别的地方定义过了,不要急着报错

——typedef   :  

递归(更难阅读和维护)

递归必须有结束条件、否则程序将崩溃

实现递归要满足两个条件:1、调用函数本身、2、设置正确的结束条件

慎用递归

#include<stdio.h>
long fact(int num);
long fact(int num)
{
    long result;
    if(num>0)
    {
        result = num * fact(num - 1);
    }
    else
    {
        result = 1;
    }
    return result;
}
int main(void){
    int num;
    printf("请输入一个数:");
    scanf("%d", &num);
    printf("%d的阶乘是:%d\n", num, fact(num));
    return 0;
}

递归解决汉诺塔问题

#include<stdio.h>
void hanoi(int n, char x, char y, char z);
void hanoi(int n, char x, char y, char z)
{
    if(n==1)
    {
        printf("%c -->%c\n", x, z);//n=1时直接从x移动到z就行了
    }
    else
    {
        hanoi(n - 1, x, z, y);//将n-1个圆盘从x借助z移动到y
        printf("%c -->%c\n", x, z);
        hanoi(n - 1, y, x, z);//把y上的借助x移动到z
    }
}
int main(void){
    int n;
    printf("请输入汉诺塔的层数:");
    scanf("%d", &n);
    hanoi(n, 'x', 'y', 'z');
    return 0;
}

分治法

大事化小、小事化了

快速排序

基本思想:通过一趟排序将待排序数据分割成独立的两部分、其中一部分的所有元素均比另一部分的元素小、然后分别对这两部分继续进行排序、重复上述步骤直到排序完成

#include<stdio.h>
void quick_sort(int array[],int left,int right)
{
    int i = left, j = right;
    int temp;
    int pivot;
    pivot = array[(left + right )/ 2];
    while (i<=j)
    {
        //从左到右找到大于等于基准点的元素
        while (array[i]<pivot)
        {
            i++;
        }
        //从右到左找到小于等于基准点的元素
        while (array[j]>pivot)
        {
            j--;
        }
        //如果i<=j、则互换
        if(i<=j)
        {
            temp = array[i];
            array[i] = array[j];
            array[j] = temp;
            i++;
            j--;
        }
    }
    if(left<j)
    {
        quick_sort(array, left, j);
    }
    if(i<right)
    {
        quick_sort(array, i,right);
    }
}
    int main(void)
    {
        int array[] = {73,108,111,118,101,70,105,115,104,67,46,99,111,109};
        int i, length;
        length = sizeof(array) / sizeof(array[0]);
        quick_sort(array, 0, length - 1);
        printf("排序后的结果是:");
        for (i = 0; i < length;i++)
        {
            printf("%d ", array[i]);
        }
        putchar('\n');
    }
    

更灵活的内存管理方式

----malloc

————申请动态内存空间

函数原型:void *malloc(size_t size);         

malloc函数像系统申请分配size个字节的内存空间(并没有初始化为0、是随机的)、并返回一个指向这块空间的指针(void类型:因为void可以转换为任意其他类型)。如果函数调用成功、返回指向申请的内存空间的指针、若调用失败、返回值是NULL。如果size参数设置为0、返回值也可能是NULL、但并不意味着函数调用失败。

----free

————释放动态内存空间

函数原型:void free(void *ptr);

free函数释放ptr参数指向的内存空间。该内存空间必须是由malloc\calloc\realloc函数申请的。否则、该函数将导致未定义行为。若ptr参数是NULL、则不执行任何操作。注意:该函数并不会修改ptr的参数的值、所以调用后它仍然指向原来的地方(变为非法空间)。

----calloc

————申请并初始化一系列内存空间

----realloc

————重新分配内存空间

内存泄漏的原因

————隐式内存泄漏(用完的内存块没有及时用free函数释放)

————丢失内存块地址

malloc还可以申请一块任意尺寸的内存空间

#include<stdio.h>

#include<stdlib.h>

int main(void)

{

    int *ptr = NULL;

    int num, i;

    printf("请输入待录入数的个数:");

    scanf("%d", &num);

    ptr = (int *)malloc(num * sizeof(int));

    for (i = 0; i < num;i++)

    {

        printf("请录入第%d个整数:", i + 1);

        scanf("%d", &ptr[i]);

    }

    printf("你录入的整数是:");

    for (i = 0; i < num;i++)

    {

        printf("%d", ptr[i]);

    }

    return 0;

    free(ptr);

}

初始化内存空间

以mem开头的函数被编入字符串标准库、函数的声明包含在String.h这个头文件中

memset————使用一个常量字节填充内存空间

memcpy————拷贝内存空间

memmove————拷贝内存空间

memcmp————比较内存空间

memchr————在内存空间中搜索一个字符

calloc

函数原型:- void *calloc(size_t nmemb, size_t size);

calloc函数在内存中动态地申请nmemb个长度为size的连续内存空间(即申请的总空间尺寸为nmemb*size),这些内存空间全部被初始化为0。

malloc与calloc的区别
calloc函数在申请完内存后,自动初始化该内存空间为零
malloc函数不进行初始化操作,里边数据是随机的
realloc

函数原型——void *realloc(void *ptr,size_t size);

以下几点是需要注意的:
- realloc函数修改ptr指向的内存空间大小为size字节

一如果新分配的内存空间比原来的大,则旧内存块的数据不会发生改变;如果新的内存空间大小小于旧的内存空间,可能会导致数据丢失,慎用!

一该函数将移动内存空间的数据并返回新的指针

一如果ptr参数为NULL,那么调用该函数就相当于调用malloc(size)

一如果size参数为日,并且ptr参数不为NULL,那么调用该函数就相当于调用free(ptr)

一除非ptr参数为NULL,否则ptr的值必须由先前调用malloc、 calloc或realloc函数返回
C语言的内存布局规律

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩展或缩小。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。

大家平时可能经常听到堆栈这个词,一般指的就是这个栈。栈是函数执行的内存区域,通常和堆共享同一片区域。
堆和栈的区别

申请方式

------堆由程序员手动申请

------栈由系统自动分配

释放方式

------堆由程序员手动释放

------栈由系统自动释放

生存周期

------堆的生存周期由动态申请到程序员主动释放为止、不同函数之间均可自由访问

------栈的生存周期由函数调用开始到函数返回时结束、不同函数之间的局部变量不能互相访问

发展方向

------堆和其它区段一样、都是从低地址向高地址发展

------栈相反、由高地址向低地址发展

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值