C语言大复习


因为最近要准备面试,所以又回来补一下基础整理一下笔记

一、宏定义

1.1、宏定义的生效环节

宏定义,就是一个预处理命令,有预处理器来完成工作,主要工作就是以下四个部分:

  1. 头文件的文件引入(#incclude);
  2. 条件编译(#if #elif #endif);
  3. 宏扩展;
  4. 行控制;

1.2、条件编译

一般来说,代码的每一行都需要被编译,但是有的时候为我们不需要一些代码,所以我们需需要在这些代码上加入条件,让只满足条件的代码进行编译。
举个常的例子:
这是一段开源库的代码


#ifdef __cplusplus 
extern "C" { 
#endif 
 
void hello();
 
#ifdef __cplusplus 
} 
#endif 

为了方便c和c++的去区分,所以在条件上添加的exten"c"这个条件,如果采用gcc进行编译,则不会生效。如果采用g++进行编译,则exten"c"这个条件就会满足,可以用c代码来调用。

包括为我们再平时使用的if else可以叫做条件编译。

1.3、平台已经编译好的宏定义

因为在一些大型系统上,需要这些定义,所以这些定义在系统上已经被广泛使用了

FILE:当前源代码文件名;
LINE:当前源代码的行号;
FUNCTION:当前执行的函数名;
DATE:编译日期;
TIME:编译时间;
#include <stdio.h>
int main()
{

	printf("file name: %s, function name = %s, current line:%d \n", __FILE__, __FUNCTION__, __LINE__);
	return 0;
}

在这里插入图片描述

1.4、宏扩展

所谓的宏扩展就是代码替换,也就是为我们平常说的 #define
主要作用为一下几点

  1. 减少重复的代码;
  2. 完成一些通过 C 语法无法实现的功能(字符串拼接);
  3. 动态定义数据类型,实现类似 C++ 中模板的功能;
  4. 程序更容易理解、修改(例如:数字、字符串常亮);

1.4.1 宏扩展的好处

  1. 不需要检查参数,更灵活的传参;
  2. 直接对宏进行代码扩展,执行时不需要函数调用;
  3. 如果同一个宏在多处调用,不会增加代码体积;

举个例子
(1)使用宏定义


#define MAX(a, b)    (((a) > (b)) ? (a) : (b))

int main()
{
    printf("max: %d \n", MAX(1, 2));
}

使用函数


int max(int a, int b)
{
    if (a > b)
        return a;
    return b;
}

int main()
{
    printf("max: %d \n", max(1, 2));
}

可能这样看不出什么,但是如果把两个整形数换成浮点数呢?
是不是在函数上就会出血现类型不匹配的错误。但是在宏定义上就不会。

1.4.2 宏扩展的#和##符号

  1. #号:把参数换成字符串;
  2. ##号:把两个参数连接起来;
#include <stdio.h>
#define STR(X) #X

int main()
{
        printf("output of 123: %s\n",STR(123));
        return 0;
}

这里用 %s输出的哈
在这里插入图片描述
这样就把参数变成了字符串

#include <stdio.h>
#define name(x,y) x##y
int main()
{
	char string[] = (hello world);
	printf("%s",name(str,ing));
	return 0;
}

在这里插入图片描述

1.5、可变参数的处理

1.5.1:可变参数

在代码中,我们使用三个点(…)来表示可变参数。使用 (VA_ARGS )来表示可变参数。

#include <stdio.h>
#define name(...) printf(__VA_ARGS__)
int main()
{
        name("this name is :%d\n",1);
        return 0;
}

我测试过了,只能打印数字。
在这里插入图片描述

1.5.2:自定义参数名

但是如果在三个点之前加入一参数名,那就要使用这个参数名来代替(VA_ARGS

#include <stdio.h>
#define name(argc...) printf(argc)
int main()
{
        name("this name is :%d\n",2);
        return 0;
}  

在这里插入图片描述

1.5.3:可变参数为0的处理

#define name(buf,argc...) printf(buf,argc)
int main()
{
        name("this name is :%d\n",3);
        return 0;
}

这样编译没啥问题,可以通过。
在这里插入图片描述
但是我们修改一下代码

#include <stdio.h>
#define name(buf,...)  printf(buf, __VA_ARGS__ )
int main()
{
        name("this name \n");
        return 0;
}

在这里插入图片描述
他会报错,
如何解决,加上两个## 注意,这里的##不是连接而是自动删除

在这里插入图片描述
Ok啦。即使是使用自己定义的参数名,也一样,需要在前面加上##。

二、指针和数组之间的区别

2.1、数组和指是如何访问数据的

**数组的访问方式:**
char a[9] = "abcdeefg";

a是一个数组,编译器里面有一个符号表a,他的地址是 4444(这也就是数组的首地址),数组后面所有的字符都可以通过这个地址+偏移量找到,编译器也不需要知道数组的总长度。
在这里插入图片描述

指针的访问方式:

char c = 'F';
char *p = &c;

p是一个指针,在编译器符号表里面有一个符号p,他的地址是4444,这个地址(4444)里面存放的内容是(5555),他会把这个内容(5555)作为指向对象向的地址,并从中取得字符F;

2.2、数组的偏移和大小

#include <stdio.h>
int main()
{
	char s[20] = "hello world!\n";
	int a = sizeof(s[0]);
	printf("每个数组的长度是%d\n",a);
	printf("这个数组的地址是:%p\n",s);
	printf("这个数组的首地址是:%p\n",&s[0]);
	printf("这个数组偏移一位的地址是:%p\n",&s[1]);
	return 0;
}

在这里插入图片描述
很明显。数组是char类型的,占用1个字节,数组的名字就是这个数组的地址,数组的第一位数据处存方的也是也这个地址,这也证明了上面的那个数组访问的方式,他是访问地址,按照数组存放数据类型偏移进行读取。char占用一字节,偏移一位就是地址往后+1。

2.3、指针偏移方式

#include <stdio.h>
int main()
{
	char *p = "hello world!\n";
	int a = sizeof(p);
	printf("每个指针的长度是%d\n",a);
	printf("这个指针的地址是:%p\n",p);
	printf("这个指针偏移一位的地址是:%p\n",p+1);
	return 0;
}

在这里插入图片描述

我是64位操作系统,这里这里我也定义的是char类型的指针,但是指针占用8个字节啊,虽然地址偏移量都一样,但是长度不一样。

2.4、指针访问数组

#include <stdio.h>
int main()
{
	char a[] = "abcdefg";
	char *p = a;
	printf("数组的第三个数是:%c\n",a[2]);
	printf("数组的第三个数是:%c\n",*(+2));
	printf("数组的第三个数地址是:%p\n",&a[2]);
	printf("数组的第三个数地址是:%p\n",*p+2);
	return 0;
}

在这里插入图片描述

三、位和字节的操作

3.1位操作

3.1.1按位或

在二进制中,只要有1,那么运算过后就是1.
符合: |
举个例子:

0100 0010

0000 0000

进行或运算,那么运算结果就是

0100 0010

3.1.2 按位与

两个数进行与运算,必须两个都是1才能是1,其余都是0
符号: &
举个例子

0100 0011

0100 1101

进行与运算,得到的结果是

0100 0001

3.1.3左移

符号 : <<
原来的二进制

0100  0001

左移一位后的二进制

1000 0010

简单理解来说,左移就是整体往左边移动。
最左边的那个0呢?移走了,先不管他。最右边需需要补上一个0。

3.1.4右移

符号: >>
道理与左移一样

0100 0001  >>   0010 0000

3.1.5 取反

符号: ~

~0011 0101= 1100 1010

就0变1,1变0;

3.1.6按位异或

符号:^

(10010011) ^ (00111101) = (10101110)

运算符 ^ 逐位比较两个运算对象。对于每个位,如果两个运算对象中有且只有 1 位 为 1, 结果为 1。其余都为0。

3.2相关用法

这个不知道怎么说,感觉说的不对,但是事实就是这么回事。我们设置偏移位为后第1位和第2位,所以我们说的打开关闭只针对第1位和第2位
这些操作一般用在寄存器(通过0和1来组成位组合)的设置上。

假设所有原来的配为1001 0110
偏移位置都是0000 0011

3.2.1 打开对应位置

在寄存器上我需要打开某一个特定值。采用或操作

1001 0110  |=  0000 0011

结果

1001 0111

这样就打开了到第1位,将它置为1,而其余的不改变

3.2.2 关闭对应位置

1001 0110 &=~ 0000 0011

这里位什么要取反,是因为要保证我们只改变这一个

0000 0011 取反 -> 1111 1100
再和 之前的1001 0110 进行或运算
1001 0110  &= 1111 1100   ->  1001 0100

这样就关闭了第2位将它置为0,如果不取反我们是不是第2位还是1而第3位和第5低8位变为0了呢,(因为后面的值是偏移值,大多都为0,所以要取反,具体的根据实际来,我目前还没有遇到过或运算不取反的)

3.2.3 切换位

就是打开关闭的,关闭打开的

1001 0110 ^= 0000 0011

结果

1001 0101

打开第1位,关闭第2位

3.2.4 提取位

unsigned long color = 0x123456;
unsigned char blue, green, red;
red = color & 0000 0011;
green = (color >> 8) & 0000 0011;
blue = (color >> 16) & 0000 0011;

RGB是按照RGB顺序排列的每个占8位,所以往后偏移就可以了(代码意思就是这样,但是原理我解释不清楚)

3.3如何判断机器字节大小端

#include <stdio.h>
int main(void)
{
    int x = 1;
    //获取int变量中第 1 个字节地址判断他是否为1
    if (*((char *)&x) == 1)
    {
        printf("little - endian\n");
 	}
    else
    {
        printf("big - endian\n");
	}
    return 0;
}

在这里插入图片描述原理是这样的:
在C语言里面只有char是占一个字节的,所以我们只能2通过char来判断首地址的。更加详细的可以百度一下。
在这里插入图片描述

4、结构体

4.1、结构体声明

结构体就好像一个人一样,你有名字,结构体也有名字,这是别人怎么认识你的最重要的东西。
你想要什么,你就可以在结构体里面定义什么。
你想要钱,你定义自己的钱,你说你不想当一个男的,太累,你想当一个女的,你直接在结构体定义成一个女的就可以。
举个例子

struct xhh
{
	int Age; 		//年龄
	char Sex[24];	//性别
	float Money;	//有没有钱
};//这个分号不能省略!不能!不能!

假如他是一个外国人,我们记不住他的名字怎么办,我们给他取一个呗

typedef struct		//typedef就好像公安局一样,有他佐证才可以这样起名字
{
	int Age; 		//年龄
	char Sex[24];	//性别
	float Money;	//有没有钱
}ATM;//把名字写在后面

4.2如何使用结构体

struct	xhh	//typedef就好像公安局一样,有他佐证才可以这样起名字
{
	int Age; 		//年龄
	char Sex[24];	//性别
	float Money;	//有没有钱
};//把名字写在后面
int main()
{
	struct xhh	s={
		.Age = 24,  //注意是逗号
		.Sex = "man",
		.Money = 1.35,
		};
	return 0;
}

这样就对结构体初始化完毕了,当然还有一些方法.

这是最标准的方法。

struct	xhh	//typedef就好像公安局一样,有他佐证才可以这样起名字
{
	int Age; 		//年龄
	char Sex[24];	//性别
	float Money;	//有没有钱
};//把名字写在后面
int main()
{
	struct xhh	s={24,"man",13.5};
	return 0;
}

其余的也不要学了,因为妈妈说:不好的不要学。

关于链表,因为面试没遇到过,所以我也不知道写啥,还有就是写不动了。写了五个多小时。累了。。
希望对大家有帮助。

  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 9
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

永不秃头的程序员

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值