C语言深度剖析 --- 【关键字】

文章目录

C语言深入解剖

1.关键字

1.1 最宽宏大量的关键字 — auto

相关:一般在代码中所定义的变量,即局部变量,默认是auto所修饰的,但一般都是省略的。不是所有的变量都是auto,它一般用来修饰局部变量。

nt main()
{
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        printf(" i = %d\n", i);
        if (1)
        {
            auto int j = 0;  //自动变量(局部变量)
            printf("1. j = %d\n", j);
            j += 1;
            printf("2. j = %d\n", j);
        }
    }
    return 0;
}
1.2 最快的关键字 — register

CPU主要进行计算硬件单元,为了方便计算,一般要先把数据从内存中读取到CPU内,所以CPU是需要具备一定的数据临时存储能力。在现代CPU内,集成了一组叫寄存器的硬件,用来进行临时数据的保存。寄存器在硬件层面上来看,它能够有效地提高运算效率,因为不需要从内存中读取数据。

register修饰变量

什么样的变量,可采用register?

1.局部的

2.不被写入的

3.高频被读取的

4.使用时不能大量使用,寄存器数量有限

#include <stdio.h>

int main()
{
    register int x = 0;
    printf("x = %d\n", &x);
    //编译后会报错,register修饰的变量不能取地址。
    //不是所有的编译器都会报错,目前用的vs2019报错了。
    return 0;
}
1.3 最名不副实的关键字 — static

相关:

static修饰全局变量时,该变量只能在本文件内访问,不能被外部文件直接访问。

static修饰局部变量时,该变量的生命周期改变,作用域不变。

static修饰函数时,该函数只能在本文件内被访问,不能在外部文件直接访问。

static有助于项目的维护,提供安全的保证。

//main.c
#include <stdio.h>

extern int b;
extern int Max(int x, int y);
//static void fr()
//{
//    int i = 0;
//    printf("i = %d\n", i );
//    i += 1;
//}
//局部变量i,因为居于临时性
//函数调用时会开辟空间并初始化
//函数结束后释放空间


static void fr()
{
    static int i = 0;
    printf("i = %d\n", i);
    i += 1;
}

int main()
{
    static int a = 10;
    printf("a = %d\n", a );
    printf("b = %d\n", b);
    int i = 0;
    for (i = 0; i < 10; i++)
    {
        fr();
    }
    return 0;
}
//test.c
#include <stdio.h>

static int b = 20;

static int Max(int x, int y)
{
    if (x > y)
        return x;
    else
        return y;
    return 0;
}
1.4 基本数据类型

相关:

定义一个变量,是需要类型的,类型决定了开辟空间的大下。

#include <stdio.h>

int main()

{
    printf("%d\n", sizeof(char));        //1
    printf("%d\n", sizeof(short));        //2
    printf("%d\n", sizeof(int));        //4
    printf("%d\n", sizeof(long));        //4
    printf("%d\n", sizeof(long long));  //8
    printf("%d\n", sizeof(float));        //4
    printf("%d\n", sizeof(double));        //8
    return 0;
}
变量命名规则

1.命名直观且可读,便于阅读和记忆。标识符最好采用英文单词或其组合,不许使用拼音。

2.命名长度应符合“min-length && max - information”原则。如MinVal比MinValueUnitOverflow好用。

3.当标识符由多个词所组成,每个词的第一个字母大写,其他全部小写。如:int MaxVal;。

4.尽量避免名字中出现数字编号,比如Val1,Val2等,除非逻辑上需要编号。

5.在多个文件之间共同使用的全局变量或函数要加范围限定符,如TEST_等。

6.作用域前缀命名,比如标识符:Global Variable,作用域前缀:g。

7.数据类型前缀命名,比如int iMax = 10;。

8.程序中不得出现仅靠大小写区分的相似的标识符。如:int x, X;int o , O, int i , I;

9.一个函数名禁止被用于其他处。如:

void test(int x)

{

int a = 1;

}

void pr(void)

{

int test = 2;

}

10.所有宏定义、枚举常数、只读变量全用大写字母进行命名,使用下划线分割。如:#define INT_MAX 100

11.局部变量中可采用通用命名方式,仅限于n、j、k等作为循环变量使用。如:i、j、k。

12.结构体被定义时必须有明确的结构体名

13.定义变量的同时别忘记初始化。

14.不同类型数据之间的运算注意精度扩展问题,一般低精度向高精度扩展。

1.5 最冤枉的关键字sizeof理解

1.5.1 常年被误认为函数
#include <stdio.h>

int main()
{
    int a = 10;
    printf("%d\n", sizeof (a));  //1
    printf("%d\n", sizeof(int)); //2
    printf("%d\n", sizeof a);    //3
    printf("%d\n", sizeof int);  //4
    //1、2、3是正确的
    //4是错误的,int是关键字不能直接使用sizeof关键字
    //sizeof不是函数,它是关键字或是操作符
    return 0;
}
1.5.2 sizeof(int)*p表示什么意思
#include <stdio.h>

int main()
{
    int* p = NULL;
    int arr[10] = { 0 };
    int* test[2];

    printf("%d\n", sizeof(p));   //4
    printf("%d\n", sizeof(arr)); //40
     printf("%d\n", sizeof(test));//8
    return 0;
}

1.6 signed、unsigned关键字

有符号整数与无符号整数
char 
    unsigned char
    signed char

short
    unsigned short[int]
    signed short[int]

int 
    unsigned int
    signed int

long
    unsigned long[int]
    signed long[int]
原码、反码、补码

创建一个变量需要在内存中开辟空间,空间的大小由不同的类型所决定,数据在所开辟内存中如何存储呢?

有符号数:

#include <stdio.h>

int main()
{
    int a = 10;
    //0000 0000 0000 0000 0000 0000 0000 1010

    int b = -20;
    //1000 0000 0000 0000 0000 0000 0001 0100 原码
    //1111 1111 1111 1111 1111 1111 1110 1011 反码
    //1111 1111 1111 1111 1111 1111 1110 1100 补码

    //补码->原码
    //方法1:先-1,然后符号位不变,按位取反。
    //方法2:先符号位不变,按位取反,然后+1。

    //对于有符号数,它一定是能够表示数值为正数或负数的,一般最高比特位表示符号位。
    //在计算机中有三种有符号数的表示方式:原码、反码、补码。
    // 任何数据在计算机中,都需要转换成二进制,因为计算机只认识二进制。
    //三种表示方式都有符号位和数值位两部分,其中符号位用1表示‘负’,使用0表示‘正’,数值位三种表示方式各有不同。

    //如果一个数值为负数,那么它应该按照原码->反码->补码进行转化。
    //原码:将二进制按照正负数形式转换成二进制即可。
    //反码:原码的符号位不变,其他按位取反。
    //补码:反码+1。

    //如果一个数值为正数,那么它的原码、反码和补码都相同。

    return 0;
}

无符号数:不需要进行转化,也不需要符号位,原反补相同。

对整型来说:数据在内存中存放的是补码。

补充:
#include <stdio.h>

int main()
{
    unsigned int y = -10;
    //1000 0000 0000 0000 0000 0000 0000 1010 原
    //1111 1111 1111 1111 1111 1111 1111 0101 反
    //1111 1111 1111 1111 1111 1111 1111 0110 补
    printf("%d\n", y); //-10
    printf("%u\n", y); //4294967286

    //变量如何存?
    //数字需先转为补码,放入空间中,看数据是否有+-号,与变量是否有无符号无关。
    // 
    //变量如何取?
    // 一定要看变量本身的类型,决定是否要看符号位。如果不用,直接将二进制转为十进制。需要的话,则需要转成原码。
    return 0;
}
大小端:

大端:按字节为单位,低权值位数据存储在高地址处,为大端。

小端:按字节为单位,低权值位数据存储在低地址处,为小端。

为什么使用补码?

计算机系统中,数值一律使用补码来表示和存储。因为,使用补码,可以将符号位和数值域一起处理,同时加减法也可一起处理(CPU只有加法器,没有减法器)。另外补码跟原码之间的相互转换,运算过程是一样的,不需要额外的硬件。

取值范围

char
unsigned char: [0,2^8-1]
signed char: [-2^7, 2^7-1] //char
//数据的取值范围,本质是多为比特位所构成的排列组合的数量。

//规律:
//无符号:[0, 2^n-1]
//有符号:[-2^(n-1),2^(n-1)-1]
问题:
//qst1
#include <stdio.h>

int main()
{
    char a[1000];
    for (int i = 0; i < 1000; i++)
    {
        a[i] = -1 - i;        //‘\0’(0)为结束
    }
    printf("%d",strlen(a)); //255
    return 0;
}



//qst2
#include <stdio.h>

int main()
{
    int i = -20;
    unsigned int j = 10;
    printf("%d\n", i+j); //-10
    return 0;
}



//qst3
#include <stdio.h>

int main()
{
    unsigned int i;
    for (i = 0; i >= 0; i++)
    {
        printf("%u\n",i); //死循环
    }
    return 0;
}

1.7 if else组合

基本语法
语法结构:
    //1
    if (表达式)
        语句;

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

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

1.C语言中’0‘表示假,非’0‘表示为真。

2.if语句的执行,它会先完成表达式中的值,得到逻辑结果,再进行判断。

1.7.1 bool变量与’零值‘进行比较
c语言中是否存在bool类型?

在c99之前,主要c90没有, 目前大部分书中都认为是没有的。但c99中引入了_Bool类型,_Bool就是一个类型,在头文件stdbool.h中,被宏重新写成了bool,为了保证兼容性。

#include <stdio.h>
#include <stdbool.h>

int main()
{
    bool test = true;
    test = false;
    printf("%d\n", sizeof(test));  //1
    return 0;
}



//源码
// stdbool.h
//
//      Copyright (c) Microsoft Corporation. All rights reserved.
//
// The C Standard Library <stdbool.h> header.
//
#ifndef _STDBOOL
#define _STDBOOL

#define __bool_true_false_are_defined 1

#ifndef __cplusplus

#define bool  _Bool
#define false 0
#define true  1

#endif /* __cplusplus */

#endif /* _STDBOOL */

1.7.2 float类型变量与’零值‘进行比较
#include <stdio.h>

int main()
{
    double x = 3.2;
    printf("%.50f\n", x); //3.20000000000000017763568394002504646778106689453125
    return 0;
}
//补充:浮点数在内存中存储不是完整存储的,十进制转化为二进制,可能会有精度损失。
//损失不是一定就减少了,也有可能增多。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

结论

1.浮点数在进行比较时,不可直接用哦’=='进行比较。

2.浮点数本身有精度损失,导致各种结果可能存在微小的差别。

两个浮点数该怎么比较?
//应该进行范围精度的比较

//1
if ((x - y) > -精度 && (x - y) < 精度)
{
    ;
}

//2 - 简洁版
if (fabs(x - y) < 精度) 
{
    ;
}
//fabs:浮点数求绝对值。
//使用之前需要包含头文件<math.h>

//精度:可用系统精度。
//使用需包含以下头文件

#include <float.h>
DBL_EPSILON //double 最小精度
FLT_EPSILON //float  最小精度
修改后:
#include <stdio.h>
#include <math.h>
#include <float.h>

int main()
{
    double x = 1.0;
    double y = 0.1;
    printf("%.50f\n", x - 0.9);
    printf("%.50f\n", y);

    if (fabs((x - 0.9) - y) < DBL_EPSILON)
    {
        printf("OK\n");
    }
    else
    {
        printf("NO\n");
    }
    return 0;
}


//两个精度的定义
#define DBL_EPSILON      2.2204460492503131e-016 // smallest such that 1.0+DBL_EPSILON != 1.0
#define FLT_EPSILON      1.192092896e-07F        // smallest such that 1.0+FLT_EPSILON != 1.0
float类型变量与’0‘的比较
#include <stdio.h>
#include <float.h>
#include <math.h>

int main()
{
    double x = 0.00000000000000000000001;
    //if(fabs(x-0.0) < DBL_EPSILON){ //1
    //if(fabs(x) < DBL_EPSILON){     //2
    if (x > -DBL_EPSILON && x < DBL_EPSILON) 
        {
            printf("OK\n");
        }
        else
        {
            prinf("NO\n");
        }
    return 0;
}
1.7.3 指针变量与’零值‘进行比较

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

int* p = NULL;
if (p == 0);    if (p != 0);    //1
if (p);            if (!p);        //2
if (p == NULL); if (p != NULL); //3
1.7.4 else与哪个if配对?
#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    if (a == 3)
        if (b == 4)
            printf("1\n");
    else
        printf("2\n");
    return 0;
}



//推荐
#include <stdio.h>

int main()
{
    int a = 1;
    int b = 2;
    if (a == 3)
    {
        {
            if (b == 4)
                printf("1\n");
        }
    }
    else
    {
        printf("2\n");
    }
    return 0;
}
1.8 switch、case、break
语法结构
switch(表达式)

{

        case 常量表达式1: 语句1;break;

        case 常量表达式2: 语句2;break;

        '

        '

        '

        case 常量表达式n: 语句n;break;

        default: 语句n+1;

}
//补充:
#include <stdio.h>

int main()
{
    int x = 1;
    switch (x)
    {
    case x:   //错误!
        printf("OK\n");
        break;
    default:
        printf("NO\n");
        break;
    }
    return 0;
}
1.9 do、while、for
//while
while(表达式){
     循环体语句;
}

//for
for(表达式1;表达式2;表达式3){
     循环体语句;
}

//do-while
do{
   循环体语句;
} 
while(表达式)
补充:
//三种循环所对应的死循环写法
while(1)
{
    ;
}


for(;;)
{
    ;
}


do
{
    ;
}while(1);
问题:
#include <stdio.h>

int main()
{
    while (1)
    {
        char c = getchar();
        if (c == '#')
        {
            break;
        }
        printf("%c\n", c);  //将\n删去即可
    }
    return 0;
}
1.9.1 break与continue区别
break:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

continue:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.10 goto关键字
#include <stdio.h>
int main()
{
    int i = 0;
    for (i = 1; i <= 2; i++)
    {
        if (i == 2)
        {
            goto end;
        }
        printf("Hello ");
    }
end: 
    printf("World!\n");
    return 0;
} 
1.11 void关键字
void能否定义变量?
#include <stdio.h>

int main()
{
    void a; //err
    return 0;
}
为什么void不能定义变量?

定义变量的本质是开辟空间。void作为空类型,理论上来说是不应该开辟空间的,就算开了空间,也仅作为一个占位符所看待。所以既然无法开辟空间,那么也无法作为正常变量所使用,编译器也就不让它定义变量。

1.11.1 void修饰函数返回值和参数
//s1
#include <stdio.h>

void print()
{
    printf("******\n");
    printf("******\n");
    printf("******\n");
}

int main()
{
    print();
    return 0;
}

//补充:如果定义一个函数或者库函数不需要返回值的话,那就可以写成void。
//如果不写void,自定义函数默认返回值是int。
//如果不写void也会让阅读代码的人产生误解,忘记写了,还是默认int呢?


//s2
#include <stdio.h>

int test1()    //函数默认不需要参数
{
    return 1;
}

int test2(void)//明确表明函数不需要函数
{
    return 1;
}

int main()
{
    printf("%d\n", test1(2));  //依然传入参数,编译器不会警告或报错。
    printf("%d\n", test2(2));  依然传入参数,编译器会警告或报错。
    return 0;
}

//补充:如果一个函数不需要参数,可以将参数列表设置为void,可以将错误明确地发现,也能让阅读代码的人看出意思。
1.11.2 void指针
#include <stdio.h>

int main()
{
    void* p = NULL; //可以
    return 0;
}

void*是指针,能够明确空间大小。

#include <stdio.h>

int main()
{
    void* p = NULL;
    int* a = NULL;
    float* b = NULL;
    p = a;
    p = b; //类型不同,但编译器没报错
    return 0;
}

//总结:void*可用来接受任何指针类型。
1.11.3 void*所定义的指针变量能否进行运算操作
//vs2019
#include <stdio.h>

int main()
{
    void* p = NULL;
    p++;     //err
    p--;     //err
    p += 10; //err
    return 0;
}

//gcc
#include <stdio.h>

int main()
{
    void* p = NULL;    //NULL在数值层面上来说,是0
    p++;
    printf("%d\n", p); //1
    p += 10;
    printf("%d\n", p); //11
    return 0;
}


//为什么不同的平台下,编译器的结果有所不同呢?
//因为大部分的编译器都是标准c,Linux是扩展c,Linuc同时也能保证标准c的运行无误。
1.12 return关键字
#include <stdio.h>

char* show()
{
    char str[] = "hello world";
    return str;
}

int main()
{
    char* a = show();
    printf("%s\n", a); //乱码
    return 0;
}


//return语句不可返回指向栈内存的‘指针’,因为该内存会在函数体结束时自动销毁。
以上代码的解释:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调用函数,形成栈帧。

函数返回,释放栈帧。

扩展1:

在计算机中,释放或者说是清除空间是否需要将数据全部清0/1?

清空数据,只需要将该数据设置为无效即可。

扩展2:

临时变量为什么有临时性?

临时变量要在一个变量栈帧结构中开辟空间并保存变量,栈帧结构在函数调用完毕后,要被释放掉。

1.13 const关键字
1.13.1 const修饰变量
#include <stdio.h>

int main()
{
    const int i = 10;
    i = 20;  //报错,变量i不可直接被修改
    return 0;
}
const修饰的变量并不是不可被修改:
#include <stdio.h>

int main()
{
    const int i = 10;
    int* p = (int*)&i;
    *p = 20;
    return 0;
}
const修饰变量的意义:

1.让编译器保护那些不希望被修改的参数,防止无意代码的修改。

2.告诉读代码的人,声明一个参数,是为了告诉用户这个参数的应用目的。

const修饰的变量,不能作为数组定义的一部分:
#include <stdio.h>

int main()
{
    const int n = 10;
    int arr[n]; //报错
    return 0;
}
1.13.2 const修饰一般变量
const int i = 1; 
int const i = 1;
1.13.3 const修饰数组
int const arr[5] = {1,2,3,4,5};
const int a[5] = {1,2,3,4,5};
1.13.4 const修饰指针
const int * p;       //p可变,p指向的对象不可变
int const * p;       //p可变,p指向的对象不可变
int * const p;       //p不可变,p指向的读写可变
const int * const p; //指针p和指向的对象都不可变 
1.13.5 const修饰函数的参数
void fun(const int *p)
1.13.6 const修饰函数的返回值
#include <stdio.h>

const int* test()
{
    static int g_val = 100;
    return &g_val;
}

int main()
{
    int *a = test();
    *a = 200;
    printf("%d\n", *a);
    return 0;
}
1.14 volatile关键字
volatile作用:

编译器对访问votile修饰的变量就不会再进行优化,从而可提供对特殊地址的稳定访问,保持内存可见性。

const volatile int a = 10;

//const负责写,volatile负责读(从内存),两者并不冲突。
1.15 extern关键字
extern作用:

声明函数或变量。

//test.h

#pragma once
#include <stdio.h>

extern int a;
extern void show();

//test.c
#include "test.h"

int a = 10;

void show()
{
    printf("hello world!");
}

//main.c
#include "test.h"

int main()
{
    show();
    return 0;
}

1.16 struct关键字

空结构体多大:

在VS2013中不可直接定义空结构体,在linux环境下为0字节,不同的编译器,处理的结果不同。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值