栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言b站bit知识点

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言b站bit知识点

目录

一、qsort函数

1.库函数qsort函数

2.自己实现的qsort函数

二、大小端字节序存储模式

三、截断与整型提升

四、字符串处理函数

1.strlen函数

2.strcpy函数

3.strcat函数

4.strcmp函数

5.strncopy函数

 6.strncat函数

7.strncmp函数

8.strstr函数

五、内存处理函数

1.memcpy函数

2.memmove函数

3.memcmp函数

4.memset函数

六、自定义数据类型

1.结构体

2.共用体(联合体)

3.枚举常量

七、函数的值传递和址传递

1.值传递:

2.址传递

*八、返回栈空间地址导致代码错误

九、缓冲文件系统的输入/输出函数

1.gets/puts函数

2.fgets/fputs函数

3.fgetc/fputc函数

4.scanf/printf函数

5.fscanf/fprintf函数

6.sscanf/sprintf函数

7.fopen/fread/fwite/fclose函数

8.fseek/ftell/rewind函数

9.strerror/perror函数

10.ferror/feof函数

十、C语言预处理

1.源文件 --> 可执行文件

 2.系统的预定义符号

3.#define定义的符号和宏

4.#define里边的#和##


一、qsort函数

1.库函数qsort函数

((void*)空指针类型:

可以接受所有类型的数据的地址,不能直接接引用和加减操作访问地址对应的值,在解引用前应进行强制类型转换(例如e1为void*类型指针,原*e1对应的数据类型为整型,正确的解引用操作为*(*int)e1)

①函数功能:实现对任意类型数据的排序

②引用头文件#include

③函数原型:void qsort(void *start,int length,int width,int(*cmp)(const void*e1,const void* e2));

start:待排序数组的首元素的地址

length:数组的元素个数

width:数组的元素大小

int(*cmp)(const void* e1,const void* e2):比较函数指针

④比较函数写法:

因为涉及到传参过程,需要保证函数的参数以及返回值类型保持一致:

int _cmp(const void* e1,const void* e2);

e1>e2返回值大于0(升序排列)

e1=e2返回值等于0

e1

当函数返回值大于0时便会进行两个元素之间的交换

⑤对不同类型数据排序的写法:

a.整型:

qsort函数:
qsort(arr,sz,sizeof(arr[0]),cmp_int);
比较函数写法:
int cmp_int(const void* e1,const void* e2)
{
    return *(int*)e1-*(int*)e2;//升序排列
或者:return *(int*)e2-*(int*)e1;//降序排列
}

b.浮点型:

qsort函数:
qsort(f,sz,sizeof(f[0]),cmp_float);
比较函数:
com_float(const void* e1,const void* e2)
{
    if(*(float*)e1==*(float*)e2)
        return 0;
    else if(*(float*)e1>*(float*)e2)//升序排列  降序排列:(*(float*)e1<*(float*)e2)
        return 1;
    else
        return -1;
}

c.结构体:

struct Stu
{
	int age;
	char name[10];
};
int cmp_struct_by_age(const void* e1, const void* e2)//比较函数定义方法
{
	(升序排列)return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;//注意访问成员变量的方法
    (降序排列)return ((struct Stu*)e2)->age - ((struct Stu*)e1)->age;
}
int main()
{
	struct Stu stu[3] = { {18,"孙悟空"},{19,"猪八戒"},{20,"唐僧"} };
	int sz = sizeof(stu) / sizeof(stu[0]);
	qsort(stu, sz, sizeof(stu[0]), cmp_struct_by_age);
	for (int i = 0; i < sz; i++)
	{
		printf("%d  %sn", (stu + i)->age, stu[i].name);
	}

2.自己实现的qsort函数
#include
void swap(char* e1, char* e2, int width)
{
	for (int i = 0; i < width; i++)
	{
		char tmp = *e1;
		*e1 = *e2;
		*e2 = tmp;
		e1++;
		e2++;
	}
}
void bubble_sort(void* base, int sz, int width, int (*cmp)(void* e1, void* e2))//注意函数指针参数
{
	for (int i = 0; i < sz-1; i++)
	{
		for (int j = 0; j < sz-1-i; j++)
		{
			if ((*cmp)((char*)base+j*width,(char*)base+(j+1)*width)>0)//强转为char*类型,加上宽度来达到控制任意数据类型地址的目的
			{
				swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
			}
		}
	}
}
int cmp_int(char* e1, char* e2)//整型交换函数
{
	if (*(int*)e1 < *(int*)e2)//降序排列
	{
		return 1;
	}
	else if (*(int*)e1 == *(int*)e2)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}
int cmp_float(void* e1, void* e2)//浮点型交换函数
{
	if (*(float*)e1 > *(float*)e2)//升序排列
	{
		return 1;
	}
	else if (*(float*)e1 == *(float*)e2)
	{
		return 0;
	}
	else
	{
		return -1;
	}
}
void Print_int(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}
void Print_float(float f[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%.1f ", f[i]);
	}
}
int main()
{
	int arr[] = { 1,3,5,7,9,2,4,6,8,10,0 };
	int sz_arr = sizeof(arr) / sizeof(arr[0]);
	float f[] = { 2.0,3.6,5.8,9.4,5.5,2.8,7.1 };
	int sz_f = sizeof(f) / sizeof(f[0]);
	bubble_sort(arr, sz_arr, sizeof(arr[0]), cmp_int);
	Print_int(arr, sz_arr);
	bubble_sort(f, sz_f, sizeof(f[0]), cmp_float);
	Print_float(f, sz_f);
	return 0;
}

二、大小端字节序存储模式

1.图示:

大端存储:低字节存放在高地址处,高字节存放在低地址处。

小端存储:低字节存放在低地址处,高字节存放在高地址处。

2.判断电脑大小端

通过共用体判断
#include
union Judge
{
	short a;//2字节
	char arr[2];//1字节*2
};
int main()
{
	union Judge judge;
	judge.a = 0x1234;
	printf("%xn", judge.arr[0]);//虽然没有对arr赋值,但作为共用体,它的内存空间已经被占用了
	printf("%xn", judge.arr[1]);
	return 0;
}
打印结果:34,12.
地字节存放在了低地址,说明当前机器存储类型为小端存储

三、截断与整型提升

1.char类型数能够表示的范围:

char类型数据占一个字节,8个比特位,最高位为符号位,能表示的数据范围为-128-127(2^7-1),规定10000000为-128.
unsigned char类型数据占一个字节,8个比特位,能表示的数据范围为0-255(2^8-1).

图示:

2.整型提升:

对于有符号数,整型提升时高位补符号位。

对于无符号数,整型提升时高位补零。

例如:

unsigned char a=200;
unsigned char b=100;
unsigned char c=a+b;
printf("%d,%d",a+b,c);
---------------------
运行结果应为300,44.
char类型a的二进制表示形式为11001000,char类型b的二进制表示形式为01100100;
二者在进行加减操作之前先进行整型提升,由于无符号数整型提升时最高位补零,因此整型提升后的结果为:
a:00000000000000000000000011001000;
b:00000000000000000000000001100100;
a+b:00000000000000000000000100101100;
无符号数原反补码相同,又c中最多能存放8个比特位,因此发生截断,截断后c内存存放00101100,对应十进制数字44.

四、字符串处理函数

一,长度不受限制的字符串处理函数

1.strlen函数

1.函数原型:size_t  strlen(const char*str);   size_t为typedef重命名的unsigned int

2.返回值:无符号整数

3.函数功能:求字符串长度

4.实现原理:遇‘’计数器停止计数,返回‘’前的字符数

例如:

int n=strlen("abcdef");
则a=6;

2.strcpy函数

1.函数原型:char* strcpy(char* dst,const char* src);

2.函数功能:将src字符串内容复制到dst字符串中,使得dst和src的内容一致.

3.实现原理:将src字符串中的‘’也复制了过去

4.返回值:返回dst字符串的首地址

例如:

char dst[]="abcdef";
char src="efg";
strcpy(dst,src);
det字符串的内容变为:"efg"

3.strcat函数

1.将字符串src连接到字符串dst后,包含‘’.

2.函数原型:char* strcat(char *dst,const char* src);

3.实现原理:从dst字符串末尾的‘’(包含‘’)开始,将src字符串连接,包含src末尾的''.

4.函数返回值:返回连接后的字符串的首地址.

例如:

char str[30] = "abcde";
char src[] = "fgghj";
char *p = strcat(str, src);
printf("%s", str);

4.strcmp函数

1.函数原型:int strcmp(const char* str1,const char* str2);

2.函数功能:比较两个字符串的大小。

3.实现原理:比较两个字符串的同一个位置字符,相等继续比较下一个位置的字符,没有遇到''前,若字符串1同一位置字符的ascii码值大于第二个字符串相同位置的字符的ascii码值,则字符串1大于字符串2,返回一个大于0的数字;反之,返回一个小于0的数字。若2者都比较到‘’的位置,则字符串1等于字符串2,返回数字0.

4.例子:

char* str1 = "abcdef";
char* str2 = "abcdee";
int ret = strcmp(str1, str2);
ret应为大于零的数

二,长度受限制的字符串处理函数

5.strncopy函数

1.函数原型:int strncpy(char *dst,const char* src,size_t num);

size_t:==unsigned int;  num待拷贝的字节数

2.函数功能:将src字符串里的num个字符拷贝到字符串dst中。

3.注意事项:拷贝num个字符后,不再dst末尾追加‘’;当拷贝数大于src实际字符数时,在dst的末尾追加‘’直至拷贝num个字符。

例子:

char str1[] = "abcdef";
char str2[] = "def";
strncpy(str1, str2, 3);
输出str1:defdef

*如果待拷贝字符数大于src实际的字符数,则在dst末尾追加''直至拷贝num个字符:
char str1[10] = "abcdefgh";
char str2[] = "def";
strncpy(str1, str2, 6);
则拷贝后dst的内容为def;
如下图:

 6.strncat函数

1.函数原型:char* strncat(char* dst, ocnst char*src, size_t num);

2.函数功能:将src中的n个字符追加到字符串dst的末尾,并在追加结束后在dst的末尾放上''际的;当录入的字符个数大于src字符串的长度时,正常进行追加后再dst末尾追加‘’后,多余的要追加的字符个数不做处理。

3.例子:

char dst[30] = "abcdefg";
char src[] = "hijklmn";
strncat(dst, src, 3);

输出结果dst:abcdefghij

7.strncmp函数

1.函数原型:int strncmp(const char* str1,const char* str2,size_t count);

2.函数功能:将str1与str2中的n个字符进行比较,原理与strcmp相似。

3.例子:

char str1[] = "abcd";
char str2[] = "abcdefg";
int ret = strncmp(str1, str2, 5);

结果:ret<0

8.strstr函数

1.函数原型char* strstr(const char*str1,const char*str2);

2.函数功能:找出str2字符串在str1字符串中首次出现的位置,(不包括str2的串结束符)。

如果找到了,返回子串第一次出现的位置的指针,如果没有找到,返回NULL。

3.例如:

char str1[]="abcdef";
char str2[]="cdef";
char* ret=strstr(str1,str2);
if(ret==NULL)
{
    return -1;//没有找到
}
else
{
    return 1;
}

五、内存处理函数

*头文件:string.h

1.memcpy函数

1.函数原型:voi* memcpy(vo

id* dst,const void*src,size_t num);

num:拷贝的字节数,单位字节。

2.函数功能:处理没有交叉的数据的拷贝实现任意类型数据间的拷贝,也可以处理交叉数据。

3.实现原理:

while (num)
{
    *(char*)dst = *(char*)src;
    ((char*)dst)++, ((char*)src)++;
    num--;
}

4.例子:

int dst[10] = { 0 };
int src[] = { 1,2,3,4,5 };
memcpy(dst, src, sizeof(src));
运行结束后dst:1,2,3,4,5

如果:
int arr[]={1,2,3,4,5,6,7,8,9};
memcpy(arr+2,arr,4*5);
memcpy无法处理此类交叉数据的拷贝,需要用到内存处理函数memmove()

2.memmove函数

1.函数原型:void* memmove(void* dst,const char* src,size_t num);

2.函数功能,实现原理:与函数memcpy类似,可用于处理交叉数据的拷贝

3.例子:

处理重叠:
int arr[] = { 1,2,3,4,5,6,7,8,9 };
memmovev(arr + 2, arr, 4 * 5);
结果arr:1,2,1,2,3,4,5,8,9

处理不重叠:
和memcpy相同

4.模拟实现:

//自己的写法:
#include
#include
#include
void* My_memmove(void* dst, const void* src, size_t num)
{
	assert(dst && src);//1,2,3,4,5,6,7,8,9,10
	void* p = dst;
	const void* pl = src;
	if ((char*)src + num < (char*)dst)//没有交叉
	{
		while (num--)
		{
			*(char*)dst = *(char*)src;
			++(char*)dst, ++(char*)src;
		}
		return p;
	}
	else//有交叉
	{
		
		void* pr = malloc(num);
		void* ptr = pr;//指向动态内存pr的指针
		
		//如何的到一摸一样的数组
		int num_cpy = (int)num;
		while (num_cpy--)
		{
			*(char*)ptr = *(char*)src;
			++(char*)ptr, ++(char*)src;
		}
		
		ptr = pr;
		while (num--)
		{
			*(char*)dst = *(char*)ptr;
			++(char*)dst, ++(char*)ptr;;
		}
		return p;
	}
}
int main()
{
	int dst[10] = { 5,5,5,5,5 };
	int src[] = { 6,6,6, };
	My_memmove(dst, src, sizeof(src));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", *(dst + i));
	}
	return 0;
}

3.memcmp函数

1.函数原型:int memcmp(const void* arr1,const void* arr2,size_t num);

2.函数功能:比较两个数据类型的num个字节是否相同,相同返回0,arr1>arr2返回大于0的数字,arr1

例如:

int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 1,2,3,3,3 };
int ret = memcmp(arr1, arr2, 4 * 4);

结果:ret=1>0

4.memset函数

1.函数原型:void* memset(void* arr,int set_char,size_t num);

其中:set_char:设置的内容;num:字节数,单位字节。

2.函数功能:为内存赋值。

3.例子:

char str[10] = { 0 };
memset(str, '#', sizeof(str));

结果str:##########

六、自定义数据类型

*结构体,枚举,联合体

1.结构体

1.定义与初始化:c语言大学使用教程。

2.使用的注意事项:

①、
typedef struct S
{
  int a;
  char name[20];
}S;
*使用了typedef关键字将struct S重命名为S,之后创建变量:
struct S s1;等价于S s1;

②、同一结构体的成员变量不能包含自身,例如:
strcut node
{
  int a;
  struct node s1;  //无法为该结构体类型申请确定大小的空间
};是错误的
要写成:
strcut node
{
  int a;
  struct node* next;  
};

3.内存对齐

①、结构体对齐规则:

*第一个成员在与结构体变量偏移量为0的地址处;

*其他成员要对齐到某个数字(对齐数)的整数倍处;

*对齐数:编译器默认的对齐数与该成员变量所占内存的较小值,VS默认值为8;

*结构体总大小为成员变量里边的最大对齐数的整数倍;如果嵌套了结构体,嵌套的结构体对

齐到自己最大的对齐数的整数倍处,结构体的整体大小就是所有对齐数的最大值(包含嵌套的结构体)的整数倍;

②、结构体默认对齐数可以用预处理指令进行修改:

#pragma	pack(2)//设置默认对齐数为2
struct S
{
	char a;
	int b;
	int c;
};
#pragma	pack()//取消设置的对齐数,还原默认数值




*未设置默认对齐数时的结构体大小为12;
*设置对齐数后的结构体大小为10;

4.位段

①、定义方式类似于结构体:

struct S
{
	int _a : 5;
	int _b : 10;
	int _c : 14;
	int _d : 22;
};

上述位段所占的存储空间为8个字节,冒号后的数字为数据所占比特位数;
在为该位段分配储存空间时,先查阅到int类型,开辟4个字节32个比特位的空间,可以存下a,b,c共29个比特位的数据,再存d时剩余的3个比特位不足以存放,再开辟新的存储空间即4个字节32个比特位存放d,因此上述位段共占8个字节的空间

2.共用体(联合体)

1.声明样式:

union S
{
  int a;
  char str[2];
};

2.共用体内成员共同占用同一块存储空间,所占存储空间的大小为成员中所占空间大小的最大值,如上述共用体所占内存空间的大小为4字节。

3.枚举常量

1.声明样式:

enum  Myenum
{  exp1,
   exp2,
   exp3……
};

2.枚举常量可以指定值也可以不指定。不指定值时枚举常量的值时从第一个成员开始依次为0,1,2……,指定成员变量的值时,以下的成员依次递增1。

①enum MyEnum
{
	ONE,//值为0
	TWO=5,
	THREE//值为6
};


②enum MyEnum
{
	ONE,//值为0
	TWO,//值为1
	THREE.。值为2
};



③enum MyEnum
{
	ONE=5,
	TWO,//值为6
	THREE//值为7
};

七、函数的值传递和址传递

1.值传递:

传递常量或者变量的数值

:例如:

值传递①:
void Modify(int a)
{
	a = 10;
}
int main()
{
	int a = 5;
	Modify(a);
	printf("%dn", a);
	return 0;
}

值传递②:
void Modify(int* p)
{
	*p = 10;
}
int main()
{
	int a = 5;
	int* p = &a;
	Modify(p);//传参是一个指针变量,所以用指针变量接收
	printf("%dn", a);
	return 0;
}//传递了指针变量p的值,在函数内部开辟了一个新的指针变量p`存放了与指针p相同的指向整型a的地址,并非址传递

2.址传递

传递常量或者变量的地址

例如:

址传递①:
void Modify(int* p)
{
	*p = 10;
}
int main()
{
	int a = 5;
	Modify(&a);//实参传递了整型常量a的地址,所以形参用指针变量接收
	printf("%dn", a);
	return 0;
}

址传递②:
void Modify(int** p)//用二级指针接收(存放一级指针变量的地址)
{
	*(*p) = 10;
}
int main()
{
	int a = 5;
	int* p = &a;
	Modify(&p);//传递了指针变量的地址
	printf("%dn", a);
	return 0;
}

3.面试题:

判断运行结果,找出代码中的错误:
#include
#include
#include
void Getmemory(char* p)//形参用指针变量接收
{
	p = (char*)malloc(100);//p在函数内部申请内存空间后未释放也无法在函数外部释放,造成了内存泄漏
	if (p == NULL)
	{
		exit(0);
	}
}
int main()
{
	char* str = NULL;
	Getmemory(str);//错误①,使用了值传递,传递了指针变量的值,在函数中不能为str分配内存空间
	strcpy(str, "Hellow,world");
	printf(str);
	free(str);
	str = NULL;
	return 0;
}

 *正确解法:

正确解法①(址传递):
#include
#include
#include
void Getmemory(char** pr)//形参设计为二级指针(接收一级指针变量的地址)
{
	*pr = (char*)malloc(100);/);//w:写的方式打开,文件存在则覆盖,文件不存在则新建
	if (pf == NULL)
	{
		printf("%sn", strerror(errno));
		return 0;
	}
	//成功打开
	//....
    fclose(pf);
    pf=NULL;

2.fread:
    函数原型:size_t fread(void* buffer,size_t Element_size,size_t Element_count,FILE* pf)
    函数功能:从pf指向的文件中读取size*count个字节存到buffer指向的内存区。
    返回值:返回Element_count(数据项个数)。若遇文件结束或出错,返回0.
    例如:#include
    #include
    #include
    int main()
    {
    	char str[30] = { 0 };
	    FILE* pf = fopen("baixiaoshuai.txt", "r");
    	if (pf == NULL)
    	{
	    	printf("%sn", strerror(errno));
	       	return 0;
	    }
	    //成功打开
	    fread(str, sizeof(char), 5, pf);//不会在字符串末尾添加''
	    printf(str);
        fclose(pf);
        pf=NULL;
	    return 0;
}
3.fwrite:
    函数原型:size_t fwrite(const void* buffer,size_t Element_size,size_t Element_count,File* pf)
    函数功能:从str所指向空间的size*count个字节输出到fp所指向的文件中。
    返回值:写到fp文件中的数据项的个数(count)
    例如:
int main()
{
	char str[] ="baixiaoshuai";
	FILE* pf = fopen("baixiaoshuai.txt", "w");
	if (pf == NULL)
	{
		printf("%sn", strerror(errno));
		return 0;
	}
	//成功打开
	fwrite(str, sizeof(char), strlen(str), pf);//从str指向的内存块中读取1*16个字节输出到pf对应的文件baixiaoshuai.txt中。
	fclose(pf);
	pf = NULL;
	return 0;
}
4.fclose:
    函数原型:int fclose(FILE* pf)
    函数功能:关闭fp指向的文件。
    返回值:成功返回0,否则返回非0。
    例如:FILE* pf = fopen("baixiaoshuai.txt", "w");
	if (pf == NULL)
	{
		printf("%sn", strerror(errno));
		return 0;
	}
	//成功打开
	fclose(pf);//关闭文件

8.fseek/ftell/rewind函数
1.fseek:
    函数原型:intt fseek(FILE* pf,long offset,int origin)
    函数功能:移动文件指针到特定的位置。
    返回值:成功返回,否则返回非0.在无法搜索的设备上,返回值未定义
    ogigin取值及其含义:SEEK_CUR---从文件指针当前位置开始偏移
                      SEEK_END---从文件指针末尾开始偏移
                      SEEK_SET---从文件指针起始位置偏移
    例如:
    #include
    int main()
    {
	    FILE* pf = fopen("text.txt", "r");//文件中存的是agcdef
	    if (pf == NULL)
	    {
		    perror("text.txt error");//输出错误信息样式:text.txt error:No Such File Or Directory.
		    return 0;
	    }
	    //成功打开
	    fseek(pf, -2, SEEK_END);
	    printf("%cn", fgetc(pf));//e
	    fclose(pf);
	    pf = NULL;
    }

2.ftell:
    函数原型:long ftell(FILE* pf)
    函数功能:获得当前文件指针的位置
    返回值:返回文件指针相对于起始位置的偏移量
    例如:
    fseek(pf, 0, SEEK_END);//将文件指针置于文件末尾
	int ch = ftell(pf);//返回文件指针相对于起始位置的偏移量
	printf("%dn", ch);
    可以很快知道文件的长度

3.rewind
    函数原型:void rewind(FILE* pf)
    函数功能:将文件指针重新定位与文件的开头
    例如:
    fseek(pf, -2, SEEK_END);//将位置指针置于末尾向前偏移2字节的位置
	int ch = ftell(pf);//返回当前文件指针的位置
	printf("%dn", ch);
	rewind(pf);//将文件指针置于文件的起始位置
	printf("%dn", ftell(pf));
	fclose(pf);//关闭文件
	pf = NULL;//将pf指针置为空

9.strerror/perror函数
1.strerror:
    函数原型:char* strerror(int errnum)
    函数功能:提供系统错误消息或打印用户提供的错误消息
    返回值:返回指向错误消息字符串的指针
    例如:
    FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)//以只读的方式打开文件
	{
		printf("%sn", strerror(errno));//失败的话则输出错误信息
		return 0;
	}
    fclose(pf);
    pf = NULL;

2.perror:
    函数原型:void perror(const char* str)
    函数功能:打印错误信息
    例如:
    FILE* pf = fopen("text.txt", "r");
	if (pf == NULL)//若打开文件失败
	{
		perror("error");//输出错误信息error:No such file or directory
		return 0;
	}
    fclose(pf);
    pf = NULL;

10.ferror/feof函数
1.ferror:
    函数原型:int error(FILE* pf)
    函数功能:检测文件流上的错误
    返回值:如果流上没有没有发生错误,返回0;否则返回非0.

2.feof
    函数原型:int feof(FILE* pf)
    函数功能:测试流上文件结束
    返回值:读到文件末尾返回非0;否则返回0


*ferror和feof常用来判断文件是否正常结束:
int main()
{
	FILE* pf = fopen("text.txt", "r");
	if (!pf)
	{
		perror("error");//打开文件失败返回输出错误信息
		return 0;
	}
	int c = 0;
	while ((c = fgetc(pf)) != EOF);
	if (ferror(pf))//文件异常结束,返回非0
		printf("文件读取错误结束n");
	else if (feof(pf))//文件正常结束返回非0
		printf("文件正常结束n");
	fclose(pf);
	pf = NULL;
}

十、C语言预处理

1.源文件 --> 可执行文件

①源文件(.c)经过编译器编译、链接器链接生成目标文件(.obj)。

②编译的过程又可以分为:预编译/预处理、编译、汇编。

   预处理阶段进行的操作有引用包含文件(#include),替换#define定义的符号,注释删除(使用空格代替注释);编译阶段进行的操作有将C语言代码转换为汇编代码,发生语法分析,词法分析,语义分析,符号汇总;汇编阶段进行的操作有把汇编代码转化为二进制值指令,形成符号表;链接阶段进行的操作有合并段表(elf格式),符号表的合并和重定位。

③如图:

 2.系统的预定义符号
①、__FILE__       //打印文件名称
②、__LINE__       //打印当前行数
③、__DATE__       //打印文件的编译日期
④、__TIME__       //打印文件编译时间
⑤、__STDC__       //如果编译器支持ANSI C,其值为1,否则未定义

例如:
	printf("%sn", __FILE__);//输出文件路径及名称
	printf("%dn", __LINE__);//输出当前的行数
	printf("%sn", __DATE__);//输出文件被编译的日期
	printf("%sn", __TIME__);//输出文件被编译的时间
    printf("%dn", __STDC__);//检查编译器是否遵循ANSC标准,如果遵循返回1,否则未定义

3.#define定义的符号和宏
1.#define定义的符号
#define  MAX    100
*在预处理阶段完成符号的替换,#define在定义符号时,最好不要在值的末尾加上分号



2.#define定义的宏
#define  NAME(LIST)    STUFF
例如:
#define  SQUARE(X)    ((X)*(X))
在预编译阶段,会用((X)*(X))来替换,加上括号的原因是考虑到运算符的优先级可能会导致运算出错,比如:
#define  SQUARE(X)   X*X
int a = SQUARE(5+1);
则a的值为11,这是因为在与编译阶段会用5+1替换X出现5+1*5+1,导致结果出错,因此选择对宏的每一个参数都加上括号。
再比如:
#define  DOUBLE(X)   (X)+(X)
int a = 10 * DOUBLE(5);
在运算时会出现10*5+5=55而导致结果出错,因此对于#define定义的宏最好在值的外侧在加上一层括号确保运算顺序的正确。即:
#define  SQUARE(X)   ((X)+(X))最好

4.#define里边的#和##
1.# 
    功能:将宏的参数插入到字符串中
    例如:
    #include
    #define	PRINT(X)	printf("the value of "#X " is %dn",X)//在预处理阶段将#X替换为字符串"X"
    int main()
    {
    	int a = 5;
    	int b = 6;
    	PRINT(a);//打印结果为the value of a is 5
    	PRINT(b);//打印结果为the value of b is 6
    	return 0;
    }
2.##
    功能:把位于##两边的符号合成一个符号
    例如:
    #define	BAIXIAOSHUAI(X,Y)	X##Y	
    int main()
    {
	    int ClassWin = 888;
	    printf("%dn", BAIXIAOSHUAI(Class, Win));//将Class,Win连接成一个符号
    	return 0;
    }

5.#undef移除宏定义  条件编译#ifdef/#endif  避免头文件被多次包含  模拟实现offsetof

1.undef
    功能:移除宏定义
    例如:   #define	MAX		20
            int main()
                {
	            printf("%dn", MAX);
            #undef	MAX//取消了MAX的定义,下一行无法打印
	            printf("%dn", MAX);//"MAX"未声明的标识符
	            return 0;
                }

2.条件编译避免头文件被多次包含
①   #ifdef __YUAN_H__
    #define	__YUAN_H__	1
    #include"yuan.h"
    #endif // __YUAN_H__

②   #pragma  once
    #include"yuan.h"

3.模拟实现offsetof(计算偏移量)的宏
#define Offsetof(struct_type,struct_mem_name)		(int)&(((struct_type*)0)->struct_mem_name)

struct S
{
	int a;
	char b;
	int c;
};
int main()
{
	printf("%dn", Offsetof(struct S, c));
	return 0;
}

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1001087.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号