学习笔记 day8 C语言:结构体与分文件

1 判断一个字符串是否是回文?

#include<stdio.h>
#include<string.h>
int huiwen(char s[],int len){
	char *p=s;
	char *q=&s[len-1];
	while(q!=p){
		if(*q!=*p){
			return 0;
		}
		p++;
		q--;
	}
	return 1;
}
int main(int argc, const char *argv[])
{
	char s[100];
	printf("请输入一个字符串:\n");	
	gets(s);
	
	if(huiwen(s,strlen(s))==1){
		printf("%s是回文字符串\n",s);
	}else{
		printf("%s不是回文字符串\n",s);
	}
	return 0;
}

2 计算一个数字有多少位为1?

#include<stdio.h>
int countOne(int x){
	int countx=0;
	while(x){
		countx++;
		x=x&(x-1);
	}
	return countx;
}
int main(int argc, const char *argv[])
{
	int n;
	printf("请输入一个数字:\n");
	scanf("%d",&n);
	printf("%d中有%d个1\n",n,countOne(n));
	return 0;
}

1 结构体

结构体是一种构造数据类型,结构体内部的成员的类型可以不一样,但是其内存空间是连续分配的。
结构体的作用:	
	1.解决不同数据类型需要多次定义的问题 
	2.返回值返回结构体可以一次返回多个类型的值。
注意:
	1. 结构体内部不能存在函数,但是可以写函数指针
	2. 结构体访问成员变量的方式:变量.成员
	3. 结构体指针访问成员的方式:指针变量->成员

2 结构体的大小

结构体字节对齐的规则:
	1.每一个结构体内部的成员分配内存时,都要和当前自身的对齐参数进行对齐。
	2.如果结构体每个成员对齐完毕之后,整个结构体的大小需要跟结构体中最大的成员进行对齐。 (32位:4对齐 64:8对齐)

3 结构体与联合体的区别

	结构体内部数据时分开放的,而联合体所有数据是放在一个地址空间内,我们只能使用其中一个数据。
	在 C 语言中,结构体(struct)是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。由于结构体将一组相关变量看作一个单元而不是各自独立的实体,因此结构体有助于组织复杂的数据,特别是在大型的程序中。
	共用体(union),也称为联合体,是用于(在不同时刻)保存不同类型和长度的变量,它提供了一种方式,以在单块存储区中管理不同类型的数据。
	简单地说,我们可以把“结构体类型”和“结构体变量”理解为是面向对象语言中“类”和“对象”的概念。

4 结构体的位域

	所谓"位域"是把一个字节中的二进位划分为几个不同的区域,并说明每个区域的位数。本质上是一种结构类型,不过其成员是按二进位分配的。每个域有一个域名,在程序中可以按域名访问对应的二进制区域。 
	位域可以把几个不同的对象用一个字节的二进制位域来表示。
	位域成员应该声明为int、unsigned int或signed int类型(short char long)。

注意事项:
	1.如果一个字节所剩空间不够存放另一位域时,应从下一字节起存放该位域。也可以有意使某位域从下一单元开始
	2.位域的长度不能大于数据类型本身的长度,比如int类型的位域不能能超过32位二进制位。
	3.位域可以无位域名,这时它只用来作填充或调整位置。无名的位域是不能使用的。


位域的特点:
	1	位域总是从字的第一个位开始;
	2	位域不能与整数的边界重叠,也就是说一个结构中所有域的长度之和不能大于字段大小。如果比字段大的话,重叠域将字段作为下一个字的开始。
	3	可以给未命名的域声明大小。例如:unsigned:bit-length,这种域在字段中提供填充值。
	4	字段中可以有未使用的位。
	5	不能使用位域变量的地址(同上面下划线的注意事项),这也意味着不能使用scanf函数将数值读入位域(可以使用中间变量赋值的方法来解决)。
	6	也不能使用位域变量的地址来访问位域。
	7	位域不能数组化。
	8	位域必须进行赋值,而且所赋的值必须在位域的大小范围之内,过大会发生不可预知的错误。
	9	位域定义中的数据类型如果是signed,那么其位数就不能少于两位(因为其中一个是符号位)。

5 大端序与小端序

大端序:高位字节存入低地址,低位字节存入高地址
小端序:低位字节存入低地址,高位字节存入高地址

如何判断:
#include <stdio.h>
typedef union{
	char a;
	int b;
}aa;
int main(int argc, const char *argv[]){
	aa a;
	a.b = 0x12345678;
	if(a.a == 0x12) {
		printf("主机是大端序\n");
	}
	else if(a.a == 0x78) {
		printf("主机是小端序\n");}}	
#include <stdio.h>
int main(int argc, const char *argv[])
{
	int x = 0x12345678;
	char *p = (char *)&x;
	if(*p == 0x12) {
		printf("主机是大端序\n");
	}
	else if(*p == 0x78) {
		printf("主机是小端序\n");
	}
	return 0;
}

6 枚举类型

enum是C语言中的一个关键字,enum叫枚举数据类型,枚举数据类型描述的是一组整型值的集合,枚举型是预处理指令#define的替代,枚举和宏其实非常类似,宏在预处理阶段将名字替换成对应的值,枚举在编译阶段将名字替换成对应的值,我们可以将枚举理解为编译阶段的宏,枚举类型属于基本数据类型,占4个字节的内存空间。

7 枚举和define的区别

1 枚举常量是实体中的一种,而宏定义不是实体。
2 枚举常量属于常量,但宏定义不是常量。
3 枚举量具有类型,但宏没有类型,枚举变量具有与普通变量相同的性质,如作用域,值等,但是宏没有。
4 #define宏常量是在预编译阶段进行简单替换,枚举常量则是在编译的时候确定其值。
5 一般在编译器里,可以调试枚举常量,但是不能调试宏常量。
6 枚举可以一次定义大量相关的常量,而#define宏一次只能定义一个。

8 C语言分文件编程

把所有函数分散在多个文件中,通常主函数在单独的文件里
为每个源文件编写一个配对的以.h作为扩展名的头文件,主函数所在的不需要,不分配内存的内容都可以写在头文件里,头文件里至少要包含配对源文件里所有函数的声明
在所有源文件里使用#include预处理指令包含所需要的头文件,配对头文件是一种需要的头文件,如果源文件里声明了头文件里声明的函数,也是需要的头文件。

9 C语言编译器

对于当前主流桌面操作系统而言,可使用 Visual C++、GCC 以及 LLVM Clang 这三大编译器。
Visual C++(简称 MSVC)是由微软开发的,只能用于 Windows 操作系统;GCC 和 LLVM Clang 除了可用于 Windows 操作系统之外,主要用于 Unix/Linux 操作系统。
像现在很多版本的 Linux 都默认使用 GCC 作为C语言编译器,而像 FreeBSD、macOS 等系统默认使用 LLVM Clang 编译器。由于当前 LLVM 项目主要在 Apple 的主推下发展的,所以在 macOS中,Clang 编译器又被称为 Apple LLVM 编译器。
MSVC 编译器主要用于 Windows 操作系统平台下的应用程序开发,它不开源。用户可以使用 Visual Studio Community 版本来免费使用它,但是如果要把通过 Visual Studio Community 工具生成出来的应用进行商用,那么就得好好阅读一下微软的许可证和说明书了。
而使用 GCC 与 Clang 编译器构建出来的应用一般没有任何限制,程序员可以将应用程序随意发布和进行商用。
MSVC 编译器对 C99 标准的支持十分有限,直到发布 Visual Studio Community 2019,也才对 C11 和 C17 标准做了部分支持。 所幸的是,Visual Studio Community 2017 加入了对 Clang 编译器的支持,官方称之为——Clang with Microsoft CodeGen,当前版本基于的是 Clang 3.8。

10 GCC的编译过程

预处理:-E (主要作用:头文件的展开,宏替换(#define),条件编译等)
	gcc -E hello.c -o hello.i
编译:-S (主要作用:将文本语言转化为汇编语言,进行语法错误检查,有错误,会报错,直接结束编译)
	gcc -S hello.i -o hello.s
汇编:-c(主要作用:将汇编语言转化为二进制语言)
	gcc -c hello.s -o hello.o
链接:(主要作用:链接相关的依赖库以及其它.o文件,生成所需要的可执行二进制文件)
	gcc hello.o -o hello

一 函数指针

函数指针:是一个指针,指针指向一个函数
类型:
返回值类型(*指针变量名)(形参);
使用场景:一般将函数指针当作回调函数来使用,也就是将一个函数当作另一个函数的参数
#include <stdio.h>
//取别名:typedef 给数据类型取别名
typedef int s32;  //给int取别名为s32
typedef int(*T)(int,int); //声明T为函数指针类型
void func()
{
	printf("hello world!\n");
}
void func2()
{
	printf("你好北京!\n");
}
//int add(int x,int y)
s32 add(s32 x,s32 y)
{
	return x + y;
}
int main(int argc, const char *argv[])
{
	//func();
	void (*p)();  //定义一个函数指针,指向的函数的返回值类型为void,参数为void的函数
	p = func;  //此处函数名表示的是函数的入口地址,使用p就相当于使用func
	p();
	
	p = func2;
	p();
	/*p = add;
	int ret = p();
	*/
	//int (*q)(int,int);
	T q;
	q = add;
	int ret = q(1,2);
	printf("ret = %d\n",ret);
	return 0;
}

二 回调函数

#include <stdio.h>
#include <time.h>
#include <stdlib.h>
typedef int(*T)(int,int);
int Max(int x,int y)
{
	return x > y ? 1 : 0;
}
int Min(int x,int y)
{
	return x < y ? 1 : 0;
}
void MaoPao(int *a,int length,T p)
{
	for(int i = 0;i < length -1;i++)
	{
		for(int j = 0 ; j < length -i-1;j++)
		{
			if(p(a[j],a[j + 1]))
			{
				int temp = a[j];
				a[j] = a[j + 1];
				a[j + 1] = temp;
			}
		}
	}

}
int main(int argc, const char *argv[])
{
	//srand(time(NULL));
	//int a[10000] = {0};
	//for(int i = 0 ; i < 10000;i++)
	//{
	//	a[i] = rand() % 1000;
	//}
	int a[] = {9,8,4,3,2,1,3,5,6,0,5,4,3,2,1};
	int length = sizeof(a)/sizeof(a[0]);
	MaoPao(a,length,Min);
	for(int i = 0; i < length;i++)
	{
		printf("%d ",a[i]);
	}
	putchar(10);
	return 0;
}

面试题:typedef和define的区别?

三 define的其它用法

#include <stdio.h>
#include <stdlib.h>
//宏函数有多条语句;({}) 和do{}while(0)
int min;
#define MAX(a,b) ({int max;if(a > b) max = a;else max = b;max;})  //最后一个语句就是整个宏的返回值
#define MIN(a,b) do{if(a < b) min = a;else min = b;}while(0) //最后一句话不是返回值

#define PRINT_MSG(msg) do{printf("%s\n",msg);return -1;}while(0)  //注意:这里的return不是宏函数的返回值,而是替换到main函数中,作为main函数的返回值
#define ERR_INTERNET_DISCONNECTED -5
#define S(n) #n  //#代表字符串化

#define STRING "helloworld"
#define NAME(a,b) a##b  //##代表标识符拼接
int main(int argc, const char *argv[])
{
	printf("%d\n",MAX(100,200));
	MIN(100,200);
	printf("min = %d\n",min);

	int *p = (int *)malloc(sizeof(int) * 5);
	if(NULL == p)
	{
		PRINT_MSG("malloc failure");
	}
	printf("hello world!\n");
	free(p);
	
	printf("%s\n",S(10));
	printf("%d\n",ERR_INTERNET_DISCONNECTED);
	printf("%s\n",S(ERR_INTERNET_DISCONNECTED));
	printf("%s\n",S(-5));
	printf("hello = %s\n",NAME(STRING,));
	printf("%s\n",NAME(STR,ING));
	return 0;
}

四 存储类型 关键字

auto:
    register:
    static:
    	1.限定作用域只能在本文件中使用(修饰的变量或者函数)
    	2.延长变量的生命周期
    const:修饰的变量是只读变量,const修饰的局部变量在栈上
    	const修饰的全局变量,在.ro段
    extern:延长变量或者函数的作用域
    volatile:保证数据每次都是从内存中获取的最新值,而不从缓存中取值,防止编译器对代码进行优化。
        使用场景:
        	1.在多线程中访问同一个变量
        	2.在使用c语言操作硬件地址的时候,这个地址就需要加volatile
                volatile int *p = (volatile int *)0xC001a000;
                *p = 1;
                #define p *((volatile int *)0xc001a000)
                p = 1;
                p = 2;
        	3.在中断处理函数和进程间都访问到的变量,
        		这个变量就适合加上volatile。

举例:

//func.c
#include <stdio.h>
static int num1 = 200;  //静态全局变量:限制其作用域只能在本文件中使用
static void func()
{
	printf("hello world!\n");
}
//main.c
#include <stdio.h>
int num = 100;
extern int num1;  //扩展其他文件的作用域到本文件中
//extern int num1 = 400;  //如果初始化,会造成变量的重复定义
extern void func();
void func1()
{
	for(int i = 0 ; i< 10;i++)
	{
		int count = 0;  //延长了其生命周期
		count++;
		if(i == 9)
		{
			printf("count = %d\n",count);
		}
	}
}
int main(int argc, const char *argv[])
{
	//printf("num1 = %d\n",num1);
	//func();
	func1();
	return 0;
}

五 内存管理

-------------------------------------------
栈区:局部变量,正在被调用的函数,形参等都在栈区,
	  操作系统自动申请自动释放,释放的时候,系统不会将
	  栈区的值清0,不初始化的化,其都是一些随机值
-------------------------------------
堆区:使用malloc分配的内存都在堆区,其特点:空间大,需要手动申请,手动释放
--------------------------------------
静态区:   |.bss:使用static修饰的未初始化的变量和全局未初始化的变量
		 |.data:使用static修饰的已初始化的变量和全局已初始化的变量
		 |.text:文本段(代码段)
		 |.ro:只读数据段
		 |	char *p = "helloworld";
		 |	const int a;  //全局变量
#include <stdio.h>
static int count; // .bss段
int num = 10; //.data段
int num2;  //.bss段
const int a2; //.ro段
int main(int argc, const char *argv[])
{
	int a; //栈区
	int *p = (int *)malloc(sizeof(int) * 10);//p:栈区    malloc:堆区
	const int a1; //栈区
	static int count; //.bss段
	static int count2 = 900; //.data

	char *s = "nihao";  //s:栈区   “nihao”://.ro段
	return 0;
}

六 结构体

6.1 结构体的简单使用

结构体是一种构造数据类型,结构体内部的成员的类型可以不一样,但是其内存空间是连续分配的。
结构体的作用:1.解决不同数据类型需要多次定义的问题 2.返回值返回结构体可以一次返回多个类型的值。
    格式:
    	struct 结构体名{
            //成员1 = 0;  //结构体在定义的时候不允许赋初值
            成员2;
            成员3...
        }变量1,变量2;
//注意:
1.结构体内部不能存在函数,但是可以写函数指针
2.结构体访问成员变量的方式:变量.成员
3.结构体指针访问成员的方式:指针变量->成员。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
struct student{    //结构体的声明,不开辟内存空间
	int age;
	char name[32];
	char sex;
};

int main(int argc, const char *argv[])
{
	//student s1;
	struct student s1;  //结构体的定义,定义一个结构体变量s1  
	s1.age = 10;
	strcpy(s1.name,"jack");
	s1.sex = 'f';

	struct student *p1 = (struct student *)malloc(sizeof(struct student));
	p1->age = 19;
	strcpy(p1->name,"xiaozhang");
	p1->sex = 'm';


	free(p1);
	p1 = NULL;
	return 0;
}

6.2 结构体的定义方式

#include <stdio.h>
struct student{   //有名结构体
	int age;
	int id;
	char name[32];
	char sex;
}p1 ={                //此处p1为变量名
	.name = "zhangsan",
	.age = 19
};
typedef struct student student;  //给struct student取别名为student

typedef struct person{
	int age;
	char name[32];
	double weight;
}person;   //此处person的含义是别名




struct{        //无名结构体
	int age;
	char name[32];
	char sex;
}p2,p3;   //此处p2,p3表示的是变量名,可以直接使用



typedef struct{        //无名结构体
	int age;
	char name[32];
	char sex;
}stu;   //给无名结构体取别名为stu
int main(int argc, const char *argv[])
{
	printf("name = %s,age =%d\n",p1.name,p1.age);
	p2.age = 19;
	p3.age = 67;

	student s1;
	person pp1;
	stu st;
	return 0;
}

6.2.1 结构体的特殊赋值方式

#include <stdio.h>
typedef struct{
	char name[32];
	char sex;
	int age;
}person;
int main(int argc, const char *argv[])
{
	person p;
	p = (person){
		"zhangsan",
			'm',
			19
	};
	//结构体数组赋值:
	//方法1:
	person p2[2] = {
		{
			.name = "zhangsan",
			.age = 19
		},
		{
			.name = "xiaozhang",
			.age = 28
		}
	};
	//方法2;
	person p3[3] = {
	[0] = {
		.name = "xiaozhang",
		.age = 29
	},
	[2] = {
		.name = "xiaogang",
		.age = 89
	}
	};
	//方法3:
	person p3[3];
	p3[0].name;
	p3[0].age;
	return 0;
}

6.3 结构体的大小

结构体字节对齐的规则:
1.每一个结构体内部的成员分配内存时,都要和当前自身的对齐参数进行对齐。
2.如果结构体每个成员对齐完毕之后,整个结构体的大小需要跟结构体中最大的成员进行对齐。
  (32位:4对齐 648对齐)

6.4 结构体的位域

注意:
    1、在结构体内部进行位域操作的时候,是从内存的最低位置开始占位,如果这个变量是有符号的,如果这个位域只有一位,它既是符号位,也是数据位。
    2、结构体位域的成员不能够取地址,但是可以对整个结构体取地址。

七 联合体(共用体

7.1 基本用法

union内部的成员共用同一块内存空间,共用其最大成员所占的内存空间,不管其有多少个成员,共用体的大小始终都是最大成员所占内存的空间。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZkHkNuf-1677223534244)(D:\1aaaasuqian\day\img\image-20221209173443463.png)]

#include <stdio.h>
union aa{
	char a;
	short b;
	int c;
};
int main(int argc, const char *argv[])
{
	union aa a;
	a.a = 10;
	a.b = 20;
	a.c = 0xaabbccdd;
	printf("a = %#x,b = %#x,c = %#x\n",a.a,a.b,a.c);

	return 0;
}

7.2 大小端以及判断

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p9qBEGBb-1677223534246)(D:\1aaaasuqian\day\img\image-20221209173531513.png)]

7.2.1 如何判断大小端?

#include <stdio.h>
typedef union{
	char a;
	int b;
}aa;
int main(int argc, const char *argv[])
{
	//方法1:
	aa a;
	a.b = 0x12345678;
	if(a.a == 0x12)
	{
		printf("主机是大端序\n");
	}
	else if(a.a == 0x78)
	{
		printf("主机是小端序\n");
	}
//方法2:
	int x = 0x12345678;
	char *p = (char *)&x;
	if(*p == 0x12)
	{
		printf("主机是大端序\n");
	}
	else if(*p == 0x78)
	{
		printf("主机是小端序\n");
	}
	return 0;
}

7.3 结构体中嵌套联合体

#include <stdio.h>
struct person  //使得结构体的通用性更强
{
	char a;
	union{
		int b;
		char c;
	};
};
int main(int argc, const char *argv[])
{
	struct person p1;
	p1.a = 'a';
	p1.b = 0xaa;
	printf("sizeof(p1) = %ld\n",sizeof(p1));
	printf("p1.c = %#x\n",p1.c);
	return 0;
}

八 枚举类型

枚举类型属于基本数据类型,占4个字节的内存空间
枚举就是有限数据的罗列,通常会代替宏定义大量的数据的使用。
 格式:
    enum 类型名{
    	 MON = 1//成员1如果不赋值,默认从0开始计数
         成员2,
         成员3,
         成员4,
         成员5...
         JAN = 100,
    	 成员n+1,
}
#include <stdio.h>
//当需要使用大量宏进行定义的时候,通常会用枚举代替
#define MONI 1
enum week
{
	MON,
	WED,
	THU,
	FRI,
	SAT,
	TUS,
	SUN
};
int main(int argc, const char *argv[])
{
	int wk = SAT;
	printf("wk = %d\n",wk);
	if(wk == FRI)
	{

	}

	enum week day;
	printf("day size = %ld\n",sizeof(day));
	day = WED;
	//day = 90;
	printf("day = %d\n",day);

	return 0;
}

#include <stdio.h>
enum leds{
	RED,
	GREEN,
	BLUE,
};
#define s(n)  case n:return #n
char* led_type_to_str(enum leds led)
{
	switch(led)
	{
		s(RED);
		s(GREEN);
		s(BLUE);
		default: return "不存在的";
	}
}
enum leds led_open(enum leds led)
{
	printf("打开%s灯\n",led_type_to_str(led));
	return led;
}
int main(int argc, const char *argv[])
{
#if 0
	char *p;
	enum leds led;
	led = led_open(RED);
	p = led_type_to_str(led);
	printf("打开%s-OK\n",p);
#endif
	printf("打开%s-OK\n",led_type_to_str((led_open(RED))));
	return 0;
}

九 gcc编译过程

预处理:-E (主要作用:头文件的展开,宏替换(#define),条件编译等)
	gcc -E hello.c -o hello.i
编译:-S (主要作用:将文本语言转化为汇编语言,进行语法错误检查,有错误,会报错,直接结束编译)
	gcc -S hello.i -o hello.s
汇编:-c(主要作用:将汇编语言转化为二进制语言)
	gcc -c hello.s -o hello.o
链接:(主要作用:链接相关的依赖库以及其它.o文件,生成所需要的可执行二进制文件)
	gcc hello.o -o hello

十 分文件编写

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

牛奶奥利奥

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

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

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

打赏作者

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

抵扣说明:

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

余额充值