C语言基础知识

C语言基础知识

参考文档:https://cplusplus.com/reference/

一、数据存储

整数存储

  • 补码形式存储
  • 大小端
    • 大端(存储)模式:数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中
    • 小端(存储)模式:数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中;
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int check_sys()
{
    int a = 1;
    return *(char*)&a;
}

int main() {
    int ret = check_sys();
    if (1 == ret)
    {
        printf("小端");
    }
    else 
    {
        printf("大端");
    }
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int check_sys()
{
    union {
        char c;
        int i;
    }u;
    u.i = 1;
    return u.c;
}

int main() {
    int ret = check_sys();
    if (1 == ret)
    {
        printf("小端");
    }
    else 
    {
        printf("大端");
    }
    return 0;
}

浮点数存储
(-1)^S * M * 2^E:(-1)^S表示符号位、M表示有效数字,大于等于1且小于2、2^E表示指数位
图片.png
IEEE 754规定保存M时,默认这个数的第一位是1,因此被舍去,只保存小数点后面的部分
E: 放:是一个无符号数 8位时取值范围0~255,11位时0~2047。如果E为负数,存入时必须加上一个中间数。8位的中间数是127,11位的是1023。例如:0.5 -> (-1) ^0 * 1.0 * 2^(-1) 则E存储的数值为 -1 + 127 = 126;
取:1、不为全0和全1:E的真实值 = E的存储值 - 127(或1023)
2、E为全0:E取值1-127(或1-1023)即为真实值,有效数字M的整数位不再是1而是0。
表示无限接近0的数字
例如:0 00000000 01100000000000000000000 -> +/- 0.011 * 2 ^(-126)
3、E为全1。表示正负无穷大数字
例如:0 11111111 00000000000000000000000 -> +/- 1.xxxx * 2 ^128

二、数组

一维数组

  • type array_name [const_n]; # const_n只能是常量
  • 数组大小计算 sizeof(arr) / sizeof(arr[0])
  • 在内存中是连续存放的

图片.png

二维数组

  • type array_name [const_n1][const_n2]; # const_n只能是常量 行可以省略,列不可以省略

  • 数组名代表首元素地址,两个例外:

    1、sizeof(数组名) - 数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小 单位是字节
    2、&数组名 - 数组名代表整个数组,取出整个数组的地址

  • 对数组进行传参,实际上传递的是数组首元素的地址 (int* arr 来接收 &arr[0])

    • arr = &arr[0]是首元素地址 &arr 是数组地址

图片.png

图片.png

三、指针

1、指针概念

  • 指针就是地址。指针变量就是一个存放地址的变量
  • 指针在32位机器下是4个字节,在64位机器下是8个字节。注:(指针的大小与类型无关)
  • 空指针 - 初始化为NULL

2、指针和指针类型

指针
大小为4/8个字节

int main()
{
    printf("%d\n", sizeof(char*));
    printf("%d\n", sizeof(short*));
    printf("%d\n", sizeof(int*));
    printf("%d\n", sizeof(double*));
    return 0;
}

图片.png
指针类型
解引用时候代表在指针进行解引用时能够访问空间的大小
eg: int* p: *p能够访问4个字节
char* p: *p能够访问1个字节
double* p: *p能够访问8个字节

int main()
{
    int a = 0x11223344;
    int* pa = &a;
    char* pc = &a;
    short* ps = &a;

    printf("%p\n", pa);
    printf("%p\n", pa+1);
    printf("%p\n", pc);
    printf("%p\n",pc+1);
    printf("%p\n", ps);
    printf("%p\n",ps+1);
    return 0;
}

图片.png

3、野指针

指针指向的位置是不可知的。

  • 指针未被初始化
  • 指针越界访问
  • 指针指向的空间释放

4、指针运算

标准:允许指针指向数组元素和指针指向数组最后一个元素后面的位置进行比较,不允许指针指向数组元素与指针指向数组第一个位置的前面进行比较

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
    int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    int i = 0;
    int sz = sizeof(arr) / sizeof(arr[0]);
    int* p = arr;
    for (i = 0; i < sz; i++)
    {
        printf("%d ", *p);
        p++;
    }
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

// 指针 - 指针的绝对值指的是两个指针之间元素的个数。前提:两个指针必须指向同一空间
int my_strlen(char* str)
{
    char* start = str;
    char* end = str;
    while (*end != '\0')
    {
        end++;
    }
    return end - start;
}
int main()
{
    char arr[] = "hello";
    int len = my_strlen(arr);
    printf("%d\n", len);
    return 0;
}

5、二级指针

二级指针就是存放指针地址的指针变量。

int a=10;

int* p1=&a;  //p1指向的类型是int

int* * p2=&p1;

// 对int* * 做解释:int*代表p2指向的对象的类型是int*,第二个*代表p2这个变量是一个指针。

6、指针数组

  • 存放指针的数组。
  • &arr表示的是“一整个数组首元素的地址”,而arr表示是数组单个首元素的地址。
  • “[]”优先级比“*”高 eg: int* p[10]=&arr;
    • int*表示该数组里面存放的是整型指针类型的元素,
    • p为数组名,
    • [10]表示数组里面有10个元素。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };
	int* parr[] = { arr1,arr2,arr3 };
	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d\n", *(parr[i] + j));
		}
	}
	return 0;
}

7、数组指针

  • 指向数组的指针,即用来存放数组的地址。
  • “[]”优先级比“*”高 eg:int(*p)[10]=&arr;
    • P前面是*,说明p是一个指针(定义变量的时候,*不是解引用操作)
    • [10]说明p指向的是一个数组,数组里面有10个元素。
    • int说明数组里面存放的是整型元素。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	
	// arr - 首元素地址
	// &arr[0] - 首元素地址
	// &arr - 数组的地址
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;	
    
    //*pa 中pa是指针变量的名字,*说明pa是指针
    //[5]代表pa指向的数组arr1是5个元素
    //char*代表pa指向的数组arr1的元素类型是char*
    char* arr1[5];
	char* (*pa)[5] = &arr1;
	printf("%p\n", (*pa));
	printf("%p\n", arr1);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>


void print1(int(*arr)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			// printf("%d ", *(*(arr + i) + j));
			printf("%d ", (*(arr + i))[j]);
		}
		printf("\n");
	}
}

int main()
{

	int arr[3][5] = {
		{1,2,3,4,5},
		{2,3,4,5,6},
		{3,4,5,6,7}
	};
	print1(arr, 3, 5);
	return 0;
}

8、const 修饰指针

const放在指针变量的左边时,修饰的是*p,
*p不能直接被改变,p可以直接改变
不能通过p去改变num的值

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
    // p和num指向同一个地址
    // *p = 10
	const int num = 10;
	int n = 100;
	const int* p = &num;
	//*p = 0; - error
	p = &n;
    // p变和n指向同一个地址,num没变还是原先地址
	// *p = 100, num = 10
    printf("%d\n", *p);
	printf("%d\n", num);
}

const放在指针变量的右边时,修饰的是p本身
p不能直接被改变,*p可以直接被改变
可以通过*p去改变num的值

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
    // p和num指向同一个地址
    // *p = 10
	const int num = 10;
	int n = 100;
	int* const p = &num;
	*p = n;
	//p = &n; - error
    // p和num还是指向同一个地址,没变
    // *p = 100, num = 100
	printf("%d\n", *p);
	printf("%d\n", num);
}

9、字符指针

将字符串赋值给一个字符指针变量p,不是把字符串的内容赋值给p,而是把字符串首字符的地址赋给了p。

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main()
{
	char* p = "abcdef";
	printf("%c\n", p);
	printf("%s\n", p);

	return 0;
}

10、数组传参和指针传参

数组传参

  • 数组传参可以是数组也可以是指针
  • 二维数组传参,函数的形参只能省略第一个[]里的数字:void test(int arr[][5]) {}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void test(int arr[])
{
	printf("it is ok! -- int arr[]\n");
}

void test1(int arr[10])
{
	printf("it is ok! -- int arr[10]\n");
}

void test2(int* arr)
{
	printf("it is ok! -- int* arr\n");
}

void test3(int arr[][5])
{
	printf("it is ok! -- int arr[][5]\n");
}

void test4(int(*arr)[5])
{
	printf("it is ok! -- int (*arr) [5]\n");
}

int main()
{

	int arr[10] = { 0 };
	test(arr);
	test1(arr);
	test2(arr);

	int arr1[3][5] = { 0 };
	test3(arr1);
	test4(arr1);

	return 0;
}

指针传参

  • 指针传参用指针接收就行
  • 形参如果是二级指针,那么实参可以是1、一级指针地址 2、二级指针3、指针数组
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

void print(int* p, int sz)
{
	int i = 0;
	for (i = 0; i < sz; i++)
	{
		//printf("%d \n", p[i]);
		printf("%d \n", *(p+i));
	}
}

int main()
{

	int arr[5] = { 1,2,3,4,5 };
	int* p = arr;
	int size = sizeof(arr) / sizeof(arr[0]);
	print(p, size);

	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>


void test(int** p)
{}

int main()
{

	int* p;
	int** p1 = &p;
	test(p1);
	test(&p);
	int* arr[2];
	test(arr);

	return 0;
}

11、函数指针

函数指针,指向函数的指针
eg:void(*signal(int, void(*)(int)))(int);
// 这个实际上是一个函数声明。
// signal 是一个函数名,(int , void(*)(int))是其两个参数,一个是int ,一个是void(*)(int)(即其中一个参数函数指针,参数为int,返回类型为void)。其返回类型也是一个函数指针,类型为void (*)(int) (指向参数为int,返回值为void 的函数指针)
//可以简化为以下代码
/*typedef void(*pfun_t)(int);
pfun_t signal(int, pfun_t);*/

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}

int main()
{

	int a = 10;
	int b = 20;
	//&函数名 和 函数名都是函数的地址
	printf("%p \n", &Add);
	printf("%p \n", Add);

	int (*pa)(int, int) = Add;
	int res = (*pa)(2, 3);
    //int res = (pa)(2, 3);
	printf("%d \n", res);

	return 0;
}

12、函数指针数组

把函数的地址存到一个数组里

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int Add(int x, int y)
{
	int z = 0;
	z = x + y;
	return z;
}
int Sub(int x, int y)
{
	int z = 0;
	z = x - y;
	return z;
}
int Mul(int x, int y)
{
	int z = 0;
	z = x * y;
	return z;
}
int Div(int x, int y)
{
	int z = 0;
	z = x / y;
	return z;
}

int main()
{

	int a = 2;
	int b = 3;
	int (*parr[4])(int, int) = { Add, Sub, Mul, Div };

	int i = 0;
	for (i = 0; i < 4; i++)
	{
		int res = parr[i](a, b);
		printf("%d\n", res);
	}

	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <assert.h>

char* my_strcpy(char* dest, const char* src);

int main()
{
    //函数指针
	char* (*pf)(char*, const char*) = my_strcpy;
    //函数指针数组
	char* (*pfArr[4])(char*, const char*) = { pf, pf };
	
	return 0;
}

char* my_strcpy(char* dest, const char* src)
{
	char* res = dest;
	assert(dest != NULL); //断言
	assert(src != NULL);//断言
	//把src指向的字符串拷贝到dest指向的空间,包含'\0'字符串
	while (*dest++ = *src++)
	{
		;
	}
	return res;

}

13、指向函数指针数组的指针

int main()
{
	int arr[10] = { 0 };
	int (*pa)[10] = &arr;//指向数组地址的指针

	//函数指针
    int (*  pf  )(int, int);
    //函数指针数组;
	int (*   pfArr[4]  )(int, int);
    //指向函数指针数组的指针
    //指向有四个元素的数组且每个元素的类型是一个函数指针int(*)(int,int) 的数组指针
	int (*  (*ppfArr)[4]  )(int, int) = &pfArr;
	return 0;

14、回调函数

把函数的地址作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,就是回调函数

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void Calc(int (*pa) (int, int))
{
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:>");
	int input = scanf("%d%d", &x, &y);
	printf("%d \n", pa(x, y));
}

int main()
{
	Calc(Add);
	Calc(Sub);
	Calc(Mul);
	Calc(Div);
	return 0;
}

15、void*

  • 可以接收任何类型的指针
  • 不可以进行解引用操作
  • 不可以进行加减整数的操作

四、自定义数据类型

1、结构体

结构体
是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量

//struct 结构体关键字 Stu - 结构体标签 struct Stu - 结构体类型
struct Stu
{
	// 成员变量
	char name[20];
	short age;
}s1, s2, s3;//s1,s2,s3三个全局的结构体变量

typedef struct Stu
{
	// 成员变量
	char name[20];
	short age;
}Stu;
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stddef.h>

struct S1 {
	double b;
	char c;
	int a;
};

void Init(struct S1* ps)
{
	ps->a = 100;
	ps->b = 3.14;
	ps->c = 'c';
}

void Print1(struct S1 tmp)
{
	printf("%lf \n", tmp.b);
	printf("%c \n", tmp.c);
	printf("%d \n", tmp.a);
	printf("------------\n");
}

void Print2(const struct S1* s1)
{
	printf("%lf \n", (*s1).b);
	printf("%c \n", (*s1).c);
	printf("%d \n", (*s1).a);
	printf("------------\n");
}

void Print3(const struct S1* s1)
{
	printf("%lf \n", s1->b);
	printf("%c \n", s1->c);
	printf("%d \n", s1->a);
	printf("------------\n");
}

int main()
{
	struct S1 s1 = { 0 };
	Print1(s1);
	Init(&s1);
	Print2(&s1);
	Print3(&s1);
	return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
struct T {
	double w;
	int a;
};
struct S {
	char a;
	struct T t;
	int c;
	float d;
	char arr[20];
};

int main()
{
	struct S s = { 'a', {12.1,10},4, 2.3, "hello"};
	printf("%c %lf %d %d %lf %s", s.a, s.t.w, s.t.a, s.c, s.d, s.arr);
	return 0;
}

空结构体
没有成员变量的结构体
struct Books {
//TODO
} book;

#include <stdio.h>
#include <string.h>

struct Books {
    char  title[50];
    char  author[50];
    char  subject[100];
    int   book_id;
};

/* function declaration */
void printBook( struct Books *book );
int main( ) {

    struct Books books[2];

    /* book 1 specification */
    strcpy( books[0].title, "C Programming");
    strcpy( books[0].author, "Nuha Ali");
    strcpy( books[0].subject, "C Programming Tutorial");
    books[0].book_id = 6495407;

    /* book 2 specification */
    strcpy( books[1].title, "Telecom Billing");
    strcpy( books[1].author, "Zara Ali");
    strcpy( books[1].subject, "Telecom Billing Tutorial");
    books[1].book_id = 6495700;

    /* print Book1 info by passing address of Book1 */
    printBook( &books[0] );

    /* print Book2 info by passing address of Book2 */
    printBook( &books[1] );

    return 0;
}

void printBook( struct Books *book ) {

    printf( "Book title : %s\n", book->title);
    printf( "Book author : %s\n", book->author);
    printf( "Book subject : %s\n", book->subject);
    printf( "Book book_id : %d\n", book->book_id);
}

内存对齐 - 让占用空间小的成员尽量集中在一起

  • 第一个成员在与结构体变量偏移量为0的地址处
  • 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处
    • 对齐数 = 编译器默认的一个对齐数与该成员大小的较小值
    • vs默认为8,gcc默认没有
  • 结构体总大小为最大对齐数(每个成员变量都有一个)的整数倍
  • 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

struct S1 {
	char c1;
	int a;
	char c2;
};

struct S2 {
	char c1;
	char c2;
	int a;
};

int main()
{
	struct S1 s1 = { '1', 1, '1'};
	printf("%d \n", sizeof(s1));
	struct S2 s2 = { '1', 1, '1' };
	printf("%d \n", sizeof(s2));
	return 0;
}

实现位段(位段的填充&可移植性)

  • 位段成员可以是int, signed int ,unsigned int, char类型
  • 按照4个字节或1个字节来开辟
  • 不跨平台,VS是从低位向高位使用
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include<stddef.h>

struct S {
	int a : 2;//比特位
	int b : 5;
	int c : 10;
	int d : 30;
};


int main()
{
	struct S s;
	printf("%d\n", sizeof(s));
	return 0;
} 

2、枚举

一一列举

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

// 定义枚举类型
enum Weekday 
{ 
//枚举的可能取值 - 常量
Sunday = 7, 
Monday = 1,
Tuesday = 2,
Wednesday,
Thursday,
Friday,
Saturday
};

int main() {
// 声明并初始化枚举变量
enum Weekday today = Sunday;

// 打印枚举变量的值
printf("Today is %d\n", today);

today = Wednesday;
printf("Today is %d\n", today);

//enum的大小都是4(就是int大小)
printf("%d \n", sizeof(today));

return 0;
}

3、联合 - 共用体

联合的成员是共用一块内存空间,一个联合变量的大小,至少是最大成员的大小,当最大成员大小不是最大对齐数的整数倍就要对齐到最大对齐数的整数倍

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

union Un {
	//至少是5,但5不是最大对齐数(8)的整数倍,所以大小为8
	char c[5];//5
	int i;//4
}u;

int main() {
	union Un u;
	printf("%d \n", sizeof(u));
	printf("%p \n", &u);
	printf("%p \n", &(u.c));
	printf("%p \n", &(u.i));
	return 0;
}

五、动态内存分配

图片.png

1、malloc + free

:::tips
都声明在<stdlib.h>
:::

void* malloc(size_t size);
这个函数向内存申请一块连续可用的空间,并返回指向这块空间的指针

  • 如果开辟成功,则返回一个指向开辟好空间的指针
  • 如果开辟失败,则返回一个NULL指针,因此malloc的返回值要做判断检查
  • 返回值的类型是void*,需要强制类型转换
  • 如果参数size为0,malloc的行为标准未定义取决于编译器

void free (void* ptr);
释放动态开辟的内存

  • 如果参数ptr指向的空间不是动态开辟的,free函数的行为未定义
  • 如果ptr是NULL指针,则函数什么也不做
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


int main() {
	//malloc 返回值是void* 类型所以要类型强制转换,参数只有一个单位是字节
	int* p = (int*)malloc(10 * sizeof(int));
	if (p == NULL)
	{
		printf("%d \n", errno);
		printf("%s \n", strerror(errno));
	}
	else {
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d \n", *(p + i));
		}
	}
	free(p);
	p = NULL;//free之后p值没有发生变化,依然可以通过p找到之前开辟的空间,所以主动赋值空指针
	return 0;
}

2、calloc

void* calloc (size_t num, size_t size);

  • 为num个大小为size的元素开辟一块空间,并且把空间的每个字节初始化为0;
  • 与malloc的区别只在于calloc会在返回地址之前把申请的空间的每个字节初始化
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


int main() {
	int* p = (int*)calloc(10,sizeof(int));//初始化为0
	if (p == NULL)
	{
		printf("%d \n", errno);
		printf("%s \n", strerror(errno));
	}
	else {
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			printf("%d \n", *(p + i));
		}
		printf("----------\n");
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
		for (i = 0; i < 10; i++)
		{
			printf("%d \n", *(p + i));
		}
	}
	free(p);
	p = NULL;
	return 0;
}

3、realloc

void* realloc (void* ptr, size_t size);

  • ptr是要调整的内存地址
  • size调整之后的大小
  • 如果ptr指向的空间之后有足够的内存空间可以追加,则直接追加,后返回ptr(内存起始位置);
  • 如果ptr指向的空间之后没有足够的内存空间可以追加,则会重新找一个新的内存区域,开辟一块满足需求的空间,并把原来内存中的数据拷贝回来,释放旧的内存空间,最后返回新开辟的内存空间地址
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>


int main() {
	int* p = (int*)calloc(5,sizeof(int));//初始化为0
	if (p == NULL)
	{
		printf("%d \n", errno);
		printf("%s \n", strerror(errno));
	}
	else {
		int i = 0;
		for (i = 0; i < 10; i++)
		{
			*(p + i) = i;
		}
	}
	int* ptr = realloc(p, 40);
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		printf("%d \n", *(ptr + i));
	}
	free(ptr);
	ptr = NULL;
	return 0;
}

六、预编译、预处理

  • 组成一个程序的每个源文件通过编译过程分别转换成没有标代码
  • 每个目标文件由链接器捆绑在一起,形成一个单一而完整的可执行程序

图片.png

编译器
预编译、预处理:

  • 注释删除:使用空格替换注释
  • 预处理指令:
    • #define->将宏定义符号替换;
    • #include:头文件可以在编译器编译之前插入到代码中
    • #ifdef:在编译源代码时,基于某些条件决定是否编译某些代码
//预编译、预处理
gcc -E test.c > test.i 
//编译 - 生成了test.s文件,将C语言代码编译成汇编代码
//1、语法分析;2、语法分析;3、语义分析;4、符号汇总
gcc -S test.i
//汇编 - 生成了test.o目标文件(相当于windows下的obj文件),将汇编代码转化成二进制代码
//形成符号表
gcc -c test.s
 gcc test.c -D 变量名=值 // 注:命令行定义

链接器

  • 合并段表
  • 符号表的合并和重定位

1、预定义符号

  • FILE 所在文件的名称(包含绝对路径)
  • __LINE__代码所在的行号
  • __DATE__日期
  • __TIME__时间
  • __FUNCTION__所在函数名字
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

int main() {
    int i = 0;
    int arr[10] = { 0 };
    FILE* pf = fopen("log.txt", "w");
    for (i = 0; i < 10; i++)
    {
        arr[i] = i;
        fprintf(pf, "file: %s date: %s time: %s i:%d \n", __FILE__, __DATE__, __TIME__, i);
    }
    fclose(pf);
    pf = NULL;
    return 0;
}

2、预处理指令

#define :宏定义 - 完全替换 最后面不要加上分号
替换规则:

  • 步骤
    • 在调用宏时,首先对参数进行检查,看看是否包含任何由#define定义的符号,如果是首先被替换
    • 替换文本随后被插入到程序中原来文本的位置。对于宏,参数名被值替换
    • 最后再次对结果文件扫描,是否包含任何由#define定义的符号,如果是,重复上述处理过程
  • 注意
    • 宏参数和#define定义中可以出现其他#define定义的变量,但是宏不能出现递归
    • 当预处理器搜索#define定义的符号时,字符串常量内容不被搜索
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

//定义符号
#define MAX 100

//定义宏
//#define name( parament-list ) stuff
#define SQUARE(X) (X) * (X)
#define COMPUTE(X) X * X

int main()
{
    int ret = SQUARE(5 + 1); // ((5+1) * (5+1))
    printf("%d \n", ret);
    int ret1 = COMPUTE(5 + 1); // 5+1 * 5+1
    printf("%d \n", ret1);
    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

#define MIN 10
#define MAX MIN * 2
#define SQUARE(X) ((X) * (X))

int main() {
	int ret = SQUARE(MIN) + MAX;
	printf("%d \n", ret);
    //"MAX = %d \n"中MAX不被搜索
    printf("MAX = %d \n", MAX);
	return 0;
}

#:宏的参数插入到字符串里

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

#define PRINT(X) printf("the value of "#X" is %d\n", X)

int main() {
	int a = 10;
	int b = 20;
	PRINT(a);// #X变为"a" printf("the value of ""a"" is %d\n", a)
	PRINT(b);// #X变为"b" printf("the value of ""b"" is %d\n", b)
	return 0;
}

##:将位于两边的符号合成一个符号

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

#define CAT(X,Y) X##Y

int main() {
	int year1 = 2019;
	printf("%d \n", CAT(year, 1));
	return 0;
}

#undef name - 移除一个宏定义

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

#define MAX 100

int main() {
	printf("%d \n", MAX);
#undef MAX
	return 0;
}

宏和函数对比

  • 宏比函数在程序的规模和速度方面更胜一筹(函数有调用和返回的开销,而宏没有)
  • 函数参数必须声明特定类型,只适合在类型合适的表达式商用;宏是与类型无关的,所以宏又是不严谨的
  • 宏会插入到程序里替换,会增加程序的长度
  • 宏没办法调试
  • 宏可能会带来运算符优先级的问题,会更容易出错

条件编译

#if 常量表达式 // 常量表达式为真 参与编译
	printf("%d \n", MAX);
#endif
> -------------------------------------
#if 常量表达式 // 常量表达式为真 参与编译
	printf("%d \n", MAX);
#elif 常量表达式
	printf("%d \n", MAX);
#else
	printf("%d \n", MAX);
#endif
> -------------------------------------
判断是否被定义
#if defined(symbol) 
... 
#elif defined(symbol1) 
...
#endif 
<=> 
#ifdef symbol 
... 
#elif defined(symbol1) 
...
#endif

#if !defined(symbol) 
... 
#endif 
<=> 
#ifndef symbol 
... 
#endif
> -------------------------------------

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>

#define MAX 100

int main() {
#ifdef MAX
	//如果MAX存在 会编译,如果不存在 不会编译
	printf("%d \n", MAX);
#endif
	return 0;
}

#include

  • 本地文件包含 #include “filename.h”

    • 查找策略 -> 先在源文件所在的目录下查找,如果未找到,编译器就像查找库函数头文件一样在标准位置查找头文件,再找不到就会提示编译错误

    • 嵌套文件包含会出现重复包含的可能性很大 导致代码冗余 以下两种方法可以避免

      //test.h文件
      
      #ifndef __TESE_H__
      #define __TESE_H__
      
      int ADD(int x,int y);
      
      #endif
      
      #pragma once
      int ADD(int x,int y);
      
  • 库文件包含 #include <filename.h>

注:参考视频:https://www.bilibili.com/video/BV1q54y1q79w?p=1&vd_source=a89593e8d33b31a56b894ca9cad33d33

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值