【C语言进阶笔记】

news/2024/11/13 4:04:29/

文章目录

  • 1. const(常量指针、指针常量)
  • 2. static
  • 3. extern
  • 4. 指针数组和数组指针
  • 5. 结构体对齐
  • 6. int / uint取值范围、二进制形式与转换、负数表示
  • 7. '\0','0',"0",0之间的区别
  • 8. 类型自动转换
  • 9. 内存结构
  • 10. 大小端
  • 参考资料

1. const(常量指针、指针常量)

int *p1;						// p1是一个指针,指向int类型
const int *p2;					// p2是一个常量指针,指向const int类型,p2可指向其他地址
int const *p3;					// p3和p2相同,只是写法不同
int *const p4 = addr;			// p4是一个指针常量,指向int类型,p4不能再指向其他地址
const int *const p5 = addr;		// p5是一个指针常量,它指向常量,const同时修饰类型与指针,p5和p5指向的地址均不可修改
  • 指针常量
    本质上一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量
    在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。
    在定义的同时必须初始化。
  • 常量指针
    常量指针本质上是一个指针,常量表示指针指向的内容,说明该指针指向一个“常量”
    在常量指针中,指针指向的内容是不可改变的指针看起来好像指向了一个常量

2. static

通常有两种情况:

  • 修饰变量
    静态全局变量:全局变量前加static修饰,该变量就成为了静态全局变量。全局变量在整个工程都可以被访问(一个文件中定义,其它文件使用的时候添加extern关键字声明 ),而在添加了static关键字之后,这个变量就只能在本文件内被访问了。因此,在这里,static的作用就是限定作用域
    静态局部变量:局部变量添加了static修饰之后,该变量就成为了静态局部变量。局部变量在离开了被定义的函数后,就会被销毁,而当使用static修饰之后,它的作用域就一直到整个程序结束。因此,在这里static的作用就是限定生命周期
  • 修饰函数
    修饰函数则该函数成为静态函数,只能被本文件中的其他函数调用,不能被同一程序的其他文件中的函数调用,即函数的作用域仅限于本文件,而不能被其它文件调用。(限定作用域

  • 在C语言中,关键字static有三个明显的作用:
    (1) 在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变
    (2) 在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所有函数访问,但不能被模块外其它函数访问。它是一个本地的全局变量
    (3) 在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。这个函数被限制在声明它的模块的本地范围内使用。

3. extern

函数的声明和定义方式默认都是extern的,即函数默认是全局的。因此,为了修饰当前文件中的内部函数,static关键字出场了。
使用static和extern修饰变量的时候,变量的生命周期是一样的,不同的是变量的作用域

关键字生命周期作用域
extern静态(程序结束后释放)外部(整个程序)
static静态(程序结束后释放)内部(仅编译单元,一般指单个源文件)
auto, register函数调用(调用结束后释放)
  • extern函数的目的
    (1)extern可置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义;
    (2)取代include "*.h"来声明函数;
    (3)extern “C”,C++在编译时为解决函数多态问题,会将函数名和参数联合起来生成一个中间的函数名,而C语言则不会,因此会造成链接时找不到对应的函数的情况,故在C++中采用extern“C”{ }声明,可以让.cpp文件使用.c文件中的函数。

4. 指针数组和数组指针

核心是看符号优先级来区分是指针还是数组:()>[]> *
一个数组里含有的元素是指针类型时,它就是指针数组。

// 注意优先级:()>[]> *
int (*p)[5];	// (数组指针)定义一个指向含有5个元素的一维数组的指针,即它是一个指针,指向一维数组
int *p[5];		// (指针数组)定义一个含有5个int型指针的数组,即它是一个数组,内部元素是指针
  • 解析
    由于优先级()>[]> *,(*p)是一个整体,表示指针,它指向int [5]数组;p[5]是一个整体,表示数组,数组元素是int *。

5. 结构体对齐

  • 对齐步骤
    (1)首先,结构体各成员对齐;
    (2)然后,结构体总体对齐。
  • 对齐规则
    (1)第一个数据成员存放的地址为结构体变量偏移量为0的地址处;
    (2)其他结构体成员存放的地址为min{自身对齐值,指定对齐值}最小整数倍的地址处;
    (3)总体对齐时,字节大小为min{所有成员中自身对齐值最大的,指定对齐值}的整数倍。
  • 基本概念
    数据自身对齐值:char为1,short为2,float为4,int通常为4等。
    结构体或类的自身对齐值:其成员中自身都旗帜最大的那个值。
    指定对齐值#pragma pack(value)时指定的对齐value。
    数据成员、结构体有效对齐值min{自身对齐值,指定对齐值}
  • 实例
    在一个四字节对齐的32位系统中,有如下结构体,其中sizeof(SmartFlag)=?
struct tagSmart
{char flag1;							// 自身对齐值为1字节int (*left_tree)[3];				// 数组指针,其本身是一个指针,因此指针的自身对齐值为4字节struct tagSmart *right_tree[2];		// 指针数组,此数组中含有2个struct tagSmart *型元素,自身对齐值为2*sizeof(struct tagSmart *)=2*4=8字节char flag3;							// 自身对齐值为1字节char flag4;							// 自身对齐值为1字节
} SmartFlag[4];

首先,进行结构体各成员对齐,遵循原则min{自身对齐值,指定对齐值}最小整数倍的地址处,其中指定对齐值为4字节:
flag1的有效对齐值为min{1,4}=1,存放地址:1*0=0,它放在结构体偏移量为0的地址处;
(*left_tree)的有效对齐值为’min{4,4}=4’,存放地址:4*0=0,不可用,4*1=4可用,即放在偏移量为4的地址处;
right_tree[2]的有效对齐值为min{8,4}=4,存放地址:4*2=8可用,即放在偏移量为8的地址处;
flag3同上,有效对齐值为1,存放地址:1*16=16,即放在偏移量为16的地址处;
flag4放在偏移量为17的地址处;
其中整体占18字节;
然后,进行总体对齐,字节大小为min{8,4}=4的整数倍,4*5=20,因此结构体对齐占20字节;
最后sizeof(SmartFlag)=20*4=80字节。

6. int / uint取值范围、二进制形式与转换、负数表示

  • int / uint的取值范围
int8:   -128 ~ 127
int16:  -32768 ~ 32767
int32:  -2147483648 ~ 2147483647
int64:  -9223372036854775808 ~ 9223372036854775807uint8:  0 ~ 255
uint16: 0 ~ 65535
uint32: 0 ~ 4294967295
uint64: 0 ~ 18446744073709551615

理解:

  • 有符号
    int8占1个字节(Byte) ,即8个二进制位(Bit);
    8个二进制位就有2^8 = 256种组合(可以存储256个数);
    int8为有符号,所以正数和负数将平分256个数。256 / 2 = 128
    负数为128个,最小为-128,正数为128个,0占一个,最大为127。
  • 无符号:
    如果是uint8(8Bit无符号-没有负数) 2^8 = 256
    0占一个数,所以最大是255。

  • 基础知识
    正数的原码、反码、补码相同。等于真值对应的机器码。
    负数的原码等于机器码,反码为原码的符号位不变,其余各位按位取反。补码为反码+1。
  • 负数的互转
    负数以绝对值的补码形式表达。
    需要先获得其绝对值的原码,再得反码,再得补码。
    原码:一个整数,按照绝对值大小转换成的二进制数,称为原码。
    反码:将二进制数按位取反(1变0,0变1),所得的新二进制数称为原二进制数的反码。
    补码:反码加1称为补码。

-29的二进制为:
绝对值:29
原码:00011101
反码:11100010
补码:11100010 + 00000001
表达:11100011

想要反推出负数的十进制,只需要按同样的方法将表达反推原码,原码的十进制数 * -1 即可:

-29的原码二进制为:
表达:11100011
反码:00011100
补码:00011100 + 00000001
原码:00011101
换算成10进制为 从低位到高位开始计算:
0 0 0 1 1 1 0 1
0*2^7 + 0*2^6 + 0*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 0*2^1 + 1*2^0
0 +0 +0 +16 +8 +4 +0 +1
=29 * -1
= -29

7. ‘\0’,‘0’,“0”,0之间的区别

其中'\0''0'是字符,"0"是字符串常量,0是整形常量。
对于字符,C语言中依据ASCII码对应进行存储,\0对应的是空字符,即ASCII码表中的0(Null),'0'对应的是表中的48,它可以参与相关运算,例如字符和数字的转换时:8+'0','8'-3
字符常量由单引号括起来;字符串常量由双引号括起来。
字符常量只能是单个字符;字符串常量则可以含一个或多个字符。

  • sizeof和strlen中的区别和用法
    \0是字符串的结束符,sizeof和strlen中到底算不算\0字符,容易搞混淆。
    sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节,在计算字符串的空间大小时,包含了结束符\0的位置;
    strlen是一个函数,用来计算字符串长度,使用时需要引用头文件#include <string.h>,不包含\0,即计算\0之前的字符串长度。

8. 类型自动转换

  • 类型转换原则
    (1)参与运算的类型不同,先转换为相同类型再运算;
    (2)数据类型向数据长度增长的方向转换,char->short->int->unsigned int ->longfloat->double
    (3)赋值运算时,赋值号右侧的类型向左侧的类型转换;
    (4)同一类型,有符号数和无符号数运算时,有符号数向无符号数转变。
  • 实例
    在C语言中下面哪些表达式为真?
unsigned int a = 20;
int b = 13;
int k = b - a;// b - a运算时首先b会转为无符号整形计算,并通过补码进行计算,得到一个很大的数,即k是一个很大的负数。
k < (unsigned int)b + a;	// 右侧是无符号数,因此k要先转为无符号数再进行运算,k转换后得到一个很大的无符号数,大于右侧,假
k < (int)(b + a);			// b+a被强制转为int,因此两边采用int型进行比较,k为负数,真
k < b + (int)a;				// 同上
  • 无符号数之间的运算
    无符号数之间进行的加减法运算,是通过补码来进行的。比如a - b,实质上是a补 + (-b补)。
    0

9. 内存结构

BSS段(存放未初始化的全局变量,不占用执行程序大小,内容由操作系统初始化)
数据段(存放已初始化的全局变量,包含初始化的静态变量,即全局变量和static变量)
代码段(存放程序执行代码的内存区域,大小在程序运行前已经确定,且通常属于只读)
堆(存放进程运行中被动态分配的内存段,大小不固定,malloc分配的为此内存)
栈(临时创建的局部变量(不含static声明的变量),在函数被调用时,其参数也会被压栈)
  • .bss段存放
    未初始化的全局变量(算数类型和指针类型);
    未初始化的static变量(不论在函数外部还是在函数内部定义);
    未初始化的常量;
    初始化为0的全局算数类型变量,初始化为NULL的全局指针类型变量;
    初始化为0的static变量(不论在函数内部定义还是在函数外部定义);
  • .data段存放
    有初始值(不为0)的全局变量;
    有初始值(不为0)的static变量,(不论static变量在函数内部定义还是在函数外部定义);
  • .text段存放
    可执行指令
    有初始值(不论初始值是否是0)的常量

10. 大小端

大端(存储)模式,是指数据的低位保存在内存的高地址中,而数据的高位,保存在内存的低地址
中;
小端(存储)模式,是指数据的低位保存在内存的低地址中,而数据的高位,保存在内存的高地
中。

参考资料

  1. 指针常量和常量指针
  2. c语言中的static关键字的作用
  3. C/C++中的 extern 和extern“C“关键字的理解和使用(对比两者的异同)
  4. 指针数组和数组指针(非常易懂)
  5. C语言–结构体内存对齐规则
  6. C语言中sizeof()和strlen()的区别(详解版)
  7. int / uint 的 取值范围、二进制表示形式、与十进制转换方法
  8. 无符号数的减法

http://www.ppmy.cn/news/147113.html

相关文章

GEE遥感云大数据林业应用典型案例实践及GPT模型应用

近年来遥感技术得到了突飞猛进的发展&#xff0c;航天、航空、临近空间等多遥感平台不断增加&#xff0c;数据的空间、时间、光谱分辨率不断提高&#xff0c;数据量猛增&#xff0c;遥感数据已经越来越具有大数据特征。遥感大数据的出现为相关研究提供了前所未有的机遇&#xf…

ChatGPT指南:身份提示术语介绍

ChatGPT是由OpenAI训练的大型语言模型&#xff0c;它可以与人类进行自然的对话&#xff0c;并根据输入的内容生成响应。在与ChatGPT交互时&#xff0c;了解一些身份提示和常用术语将有助于您更好地理解ChatGPT生成的响应。 身份提示 在与ChatGPT进行对话时&#xff0c;有时候…

'ARM::CMSIS:DSP:1.5.2' component is not available for target 'nrf51422_xxac_s110', pack

这是安装包选择的问题&#xff1b;自己主要是在调nrf蓝牙的时候遇到这个问题&#xff1b;nrf中有许多的安装包和固件&#xff0c;不同版本下调试&#xff0c;大多数时候会有问题&#xff1b; 下面是自己解决此问题的一些办法。

你需要来自XXX的权限才能对此文件夹进行更改 win10

windows 10 删除文件夹出现问题&#xff1a; 你需要来自XXX的权限才能对此文件夹进行更改 你需要来自SYSTEM的权限才能对此文件夹进行更改 解决流程&#xff1a; 右击打开此文件夹属性&#xff0c;如下图&#xff0c;点击高级按钮 出现下图&#xff0c;点击更改&#xff1a;…

零基础教程:戴尔服务器raid10配置教程

1、新建VD 硬件连接和怎么进入到raid配置页面&#xff0c;可以参考我之前写的“零基础教程&#xff1a;戴尔服务器raid5配置教程”这边文章&#xff0c;里面有详细的介绍&#xff0c;这里直接从配置界面开始讲起。其实raid10配置和raid5配置大同小异&#xff0c;基本参考raid5…

hdlbits Exams/review2015 fancytimer 答案

与该题的上一题的实现方式类似&#xff0c;采用了不同的实现方式。参考了日拱一卒的写法。 不同于自己写的上一题的解法&#xff0c;为了实现及时输出变换&#xff0c;在无触发条件的语句块中检测data&#xff08;之前的解法为在时钟上升沿到来时检测data&#xff09;&#xf…

【EDA课程设计】FPGA交通信号灯的设计(含动画视频、超详细思路/步骤分析、完整代码与效果详解)

目录&#xff1a; &#x1f335;&#x1f335;&#x1f335;前言一、题目二、功能介绍三、视频展示四、代码展示五、设计思路六、模块结构1、首先通过分频&#xff0c;得到2HZ的时钟。2、第一个always实现&#xff1a;清零信号&#xff0c;通过SW[0]使南北信号灯持续为红灯、东…

【verilog学习22】HDLBits:Circuits_Sequential Logic_Building Larger Circuits

【HDLBits】Circuits_Sequential Logic_Building Larger Cirsuits I Counter with period 1000 (Exams/review2015 count1k)1.代码编写2.提交结果3.题目分析 II 4-bit shift register and down counter (Exams/review2015 shiftcount)1.代码编写2.提交结果3.题目分析 III FSM&a…