C语言学习总结

1. static关键字

作用是什么?其修饰的变量在内存中如何存储的?
static意为静态,可以修饰局部变量、全局变量、函数。

**修饰局部变量:**局部变量是在函数体内定义的变量,普通的局部变量存在内存的栈区,函数调用完后,这个变量就会被销毁,下一次调用时,原来赋予的值就不存在了。
如果利用static去修饰局部变量,在已初始化的static局部变量就会被存放到内存数据段的data区中,没有被初始化的static局部变量会被放到数据段的bss区中。
所以如果我们希望多次使用一个局部变量且保持它的值不会丢失的话,就可以用static关键字修饰它,使它存储于静态区中。

**修饰全局变量:**全局变量是在函数体外定义的变量,无论是否初始化都存放在数据段中,没有初始化的就存放在数据段的bss区中,已初始化的就存放在数据段的data区中。
普通的全局变量,除了可以被本文件识别,还可以被本工程的其他文件识别,如果加上extern的话,它就可以被本工程文件的其他文件调用了。但是如果我们想在本工程的
其他文件定义同名的全局变量,就会出现冲突了,这个时候我们就可以使用static关键字去修饰这个全局变量,让它仅可以在本文件中识别和使用,不会影响其他文件定义同命变量。

**修饰函数:**变成静态函数,只能在声明它的地方可见。和全局变量类似,普通的函数可以被本工程的其他文件识别,如果再想在本工程下的其他文件定义同名的函数,就会出现冲突。

在这里插入图片描述

2.指针数组与数组指针

数组:是可以存储相同数据类型的一片连续内存空间,函数名指向这段内存空间的首地址。
**指针数组:**是一个数组,所有数组元素为指针,存放的是地址。指针的大小由系统决定(即数组元素的占用内存大小固定),在32位系统中,指针占4个字节,在64位系统中,指针占8个字节。int array[]是存放整型的数组,char array[]是存放字符型的数组,float array[]是存放浮点型的数组,int *array[]是存放整型指针的数组,char *array[]是存放字符指针的数组。

指针:是一种变量,和其他变量不同的是,其指向某个内存空间的首地址。
**数组指针:**是一种指针,指向数组首地址的指针变量,长度固定,由操作系统决定。

指针数组的定义:
int *array[5];
其中数组元素array[0],array[1],array[2]…都指向某个地址,可结合下图理解。
在这里插入图片描述

数组指针的定义:
int (ptr)[5];
ptr指向的数组首地址,数组有五个元素,可结合下图理解。注意:[]的优先级比
高,在定义数组指针的时候必须加括号使得*和ptr先结合。

在这里插入图片描述

3.全局变量可不可以被定义在被多个.c文件包含的头文件中?

关键在于看我们怎么使用。
1.可以,在不同的.c文件中使用static对该全局变量进行声明,前提是只在其中一个.c文件对该变量进行赋值,这时连接不会出错。
为什么要声明为static的原因如下:
每个.c⽂件,会编译成⼀个obj,如果在头⽂件中定义,⽽不是声明,那么每个引⽤这个头⽂件的obj中都会有⼀个这个变量的实例,连接的时候就会报重复定义出错了。
static使得该全局变量只在本文件中有效。

**2.不可以,**程序在编译时,#include头文件的内容会完整导入.c文件中,包括被定义在头文件的全局变量,这就会使得这个全局变量被重定义,造成编译出错。

解决办法:
我们都知道变量和函数的定义最好写在.c文件中,而变量和函数的声明才放到头文件中。可将全局变量定义在被多次使用的某个C文件,在头文件中庸extern关键字进行外部变量声明,这样其他.c文件就可以使用该全局变量了。

4.什么是回调函数? 为什么要使用回调函数?

回调函数就是通过函数指针调用的函数,当我们把函数的指针(地址)作为函数参数进行调用的时候,我们就说它是回调函数。
回调:可理解为将一段可执行的代码像参数那样传递给其他函数,这段代码可在一定条件下被调用执行。

回调函数一般用于执行具体的操作,需要用户根据实际项目需求进行编写,无法封装到库函数里面。因此库函数提供一个函数指针作为入口参数,主程序将回调函数像参数一样传入库函数。这样我们只需要改库函数的参数(回调函数),就可以实现不同的功能,并且不需要修改库函数的代码,确保了应用层和库函数的解耦。

5.C语言十进制数换成二进制数

算法步骤:
1、将需要转化的数除以2,得到商和余数k1
2、在将商除于2,得到余数k2
3、重复2的步骤,直至商为0,得到余数kn
而得到的二进制数即为所有余数组合,从后面读起,即kn k(n-1) …… k2 k1

例如:将20转化为二进制数
20/2 商为10 余数为0 k1
10/2 商为5 余数为0 k2
5/2 商为2 余数为1 k3
2/2 商为1 余数为0 k4
1/2 商为0 余数为1 k5
故二进制数为:10100

6.什么是静态库, 什么是动态库, 他们怎么使用, 有什么区别?

库是一种可以执行代码的二进制形式,可被操作系统载入内存执行。调用别人的库时,我们需要遵守许可协议,使用库可以为我们节省大量的时间,提高开发效率。Linux下库文件分两种,静态库和动态库,均有.o文件生产。

(1)静态库

静态库文件名的命名方式是“libxxx.a”,库名前加”lib”,windows和linux下都是后缀用”.a”,“xxx”为静态库名,windows下的静态库名也叫libxxx.a;
链接时间: 静态库的代码是在编译过程中被载入程序中。
链接方式:静态库的链接是将整个函数库的所有数据都整合进了目标代码。这样做优点是在编译后的执行程序不在需要外部的函数库支持,因为所使用的函数都已经被编进去了。缺点是,可执行文件占用内存空间较大,且如果所使用的静态库发生更新改变,你的程序必须重新编译。

(2)动态库

动态库的命名方式与静态库类似,前缀相同为“lib”,linux下后缀名为“.so(sharedobject)”即libxxx.so;而windows 下后缀名为“.dll(dynamic linklibrary)”即libxxx.dll;
链接时间:动态库在编译的时候并没有被编译进目标代码,而是当你的程序执行到相关函数时才调用该函数库里的相应函数。这样做缺点是因为函数库并没有整合进程序,所以程序的运行环境必须提供相应的库。优点是动态库的改变并不影响你的程序,所以动态函数库升级比较方便。

(3)使用静态库或动态库

 gcc  main.c  -o  myapp -L  lib_path  -lname

-L 就是要告诉编译链接器,把库文件链接进来;-lname的name要去掉库文件前缀和后缀。

(4)区别

它们两个还有很明显的不同点:当同一个程序分别使用静态库,动态库两种方式生成两个可执行文件时,静态链接所生成的文件所占用的内存要远远大于动态链接所生成的文件。这是因为静态链接是在编译时将所有的函数都编译进了程序,而动态链接是在程序运行时由操作系统帮忙把动态库调入到内存空间中使用。另外如果动态库和静态库同时存在时,链接器优先使用动态库。

7.Linux 32位系统, 结构体对齐, 如果按照一字节对齐怎么做?

对于结构体对齐,我们需要了解结构体的自身对齐值、指定对齐值、有效对齐值,进而了解其成员在内存中内放的原理,这样我们就可以计算他们在内存所占用的内存大小。

自身对齐值:结构体变量中每个成员自身需要占用字节的大小

指定对齐值:由宏#pragma pack(N)指定的值,N必须是2的幂次方,如1, 2, 4, 8, 16等。如果没有通过宏#pragma pack指定,那么32位的Linux主机默认指定对齐值位4,64位的Linux主机默认指定对齐值为8,ARM CPU默认指定对齐值为8。

有效对齐值:结构体成员进行自身对齐时的有效对齐值为自身对齐值和指定对齐中较小的一个。

结构体圆整时,为所有成员中自身对齐的最大的与指定对齐值较小的一个

结构体对齐的存储结构如下:对于同一个结构体,成员的顺序不同,结构体对齐所占的内存也可能不同,使用合适的顺序可以节省内存空间。
在这里插入图片描述
在这里插入图片描述

如果按照一字节对齐:
使用下面代码对同一个结构体进行四字节和1字节对齐测试

#include <stdio.h>
#define BYTE1  __attribute__((packed, aligned(1)))  //aligned(1):1字节对齐
typedef struct   //32位系统默认4字节对齐
{
    int num1;
    char ch1;
    int num2;
    char ch2;
}Str_four;
typedef struct
{
    int num1;
    char ch1;
    int num2;
    char ch2;
}BYTE1 Str_one;
int main()
{
    printf("str_four: %ld\n", sizeof(Str_four));
    printf("str_one: %ld\n", sizeof(Str_one));
}

运行结果如下:

str_four: 16
str_one: 10

可见如果使用了1字节对齐,结构体成员变量按照顺序充分使用内存空间的每一个字节。

8.位域是怎么表示? 位域如何定义?在哪些地方使用?

(1)位域

位域是在进行数据存储时,并不需要用到一个完整的字节去存储数据,只需要一个或多个二进制位就可以实现所需要的数据存储。例如使用一个变量作为开关时,只需要一个二进制位的0和1就能表示出来。为了节省内存空间,C语言提供了一种这样的数据结构叫做“位域”或“位段”,所谓的位域就是将一个字节划分为几个区域,每个区域占一个或多个二进制位。每个域有对应的域名,通过域名我们可以对域进行操作。这样我们就可以将几个对象放到一个字节里面用二进制位域表示。

(2)位域的定义

位域的定义和结构体定义类同如下:

struct 位域结构体名
{位域列表};
其中位域列表的表示形式为:类型说明符 位域名:位域长度;

例如:
struct bs
{int a:3;int b:2;int c:3}data;

data声明为bs类型的变量,共占1个字节,其中a占3位,b占2位,c占3位。
位域声明方式和结构体一样,有三种方式:先定义后声明、同时定义说明、直接声明。
且可定义一个指针,可指向结构体那样指向位域,从而访问和修改其成员的值。

(3)可使用位域的地方

当我需要使用到多个标志变量进行判断时,可使用位域变量来存储多个标志变量的值,可大大节省内存空间。例如在MQTT上云时,我们需要判断网络连接以及数据publish是否成功,数据发送失败存入数据库时,也需要判断是否成功,这时我们就需要用到多个标志变量。如果我们用三个整型的变量去作为标志变量,在32位系统下,每个整形int变量就用到4个字节,而3个标志变量就需要12个字节,这很显然浪费了内存空间,因为我们只需要设置为0和1的状态。如果我们使用一个位域变量就只需用到一个字节的3个位,由原来的12个字节大大减少到1个字节。

9.指针常量和常量指针

指针常量

#include <stdio.h>

int main() {
    int n = 10;
    int b = 11;

    int * const p = &n;
    p++;
    printf("%d", *p);
    return 0;

}

编译时报错

常量的指针

//表示形式1
#include <stdio.h>

int main() {
    int n = 10;
    int b = 11;

    int const *p = &n;
    p++;
    printf("%d", *p);
    return 0;

}
//表示形式2
#include <stdio.h>

int main() {
    int n = 10;
    int b = 11;

    const int *p = &n;
    p++;
    printf("%d", *p);
    return 0;

}

输出结果为:11

10.底层实现printf()函数

#include <stdio.h>
#include <stdarg.h>


void  my_priintf(const char *format, ...)
{
	va_list ap;//定义一个指针类型,指向参数列表,即...
	va_start(ap, format);//初始化ap,format是第一个参数(最左边参数,也叫固定参数),
	//printf的参数压栈方式是从左到右,所以使用的时候最开始是最左边的参数,
	//所以va_start()的功能是将第一个可变参数的指针给ap,即...最左边第一个参数的地址.
	
	while(*format)//遍历固定参数字符串
	{
		char ret = *format;
		if(ret == '%')//遇到%号停下来
		{
			switch(*++format)//判断类型
			{
				case 'c':
				{
					char ch = va_arg(ap, int);
					putchar(ch);//输出字符
					break;
				}	
				case 's':
				{
					char *pc = va_arg(ap, char *);
					while(*pc)
					{
						putchar(*pc); 
						pc++;
					}
					break;
				}	
				case 'd':
				{
					int in = va_arg(ap, int);
					putchar(in + '0');
					break;
				}
			}
			
		}
		else
		{
			putchar(*format);
		}
		format++;
	}
	va_end(ap);//清空参数列表
}


int main()
{
	my_priintf("hello %c %d %s\n", 'Z', 6, "cx");
	return 0;
}

11.库函数和系统调用的区别

在这里插入图片描述
库函数:C的标准库提供的函数,有些库函数会调用系统调用,有些库函数不调用系统函数,但是C库函数要操作硬件的话一定要调用系统调用。库函数具有可移植性,库函数中的文件IO带缓存。库函数调用了系统调用的时候会从用户态切换到内核态,在Linux下可用time命令查看一个命令在用户空间和内核空间所使用的时间。

系统调用:由操作系统提供的函数接口,如open() 、write() 、read()等,是为了操作底层硬件而为上层库函数而提供的接口,系统调用不带缓存。系统调用的内存在系统空间分布。

12.用联合体判断大小端字节序

#include <stdio.h>

typedef union sb_s{
		int a;
		char b;
		short c;
	}sb_t;

int main()
{
	sb_t sb;
	sb.a = 0x12345678;
	
	printf("%x\n", sb.b);
	//78LSB
	//12MSB
}

10.HTTP回复状态码都代表什么?

(1)HTTP回复状态码是什么?

状态码是客户端向服务器端发送请求的时候,描述返回的请求结果的参数,通过HTTP回复状态码,用户就可以知道这次请求是正常还是异常错误。

状态码 = 3位数字 + 原因短语

(2)状态码的类别以及常见的HTTP回复状态码

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值