分文件编程、分配内存的方式、存储类型、枚举类型

分文件编程、分配内存的方式、存储类型、枚举类型

一、分文件编程

实际开发过程中,会根据函数的功能不同,按照模块,将函数拆分成多个文件来处理。

.c 叫做源文件 .h 叫做头文件

.c 中放的是函数的定义, .h 放的是函数的声明

编译时,需要将所有的.c 文件都编译上

main.c

#include <stdio.h>
//如果包含的是系统提供的头文件 用 <>
//如果包含的是自己的头文件 需要用 ""
#include "public.h"
//也可以将自己的头文件 放在系统的 /usr/include 路径下
//然后就也可以使用 <> 来包含自己的头文件了  但是需要 sudo

int main(int argc, const char *argv[])
{
	//一般情况下 main函数中只负责函数的调用
	int a = 10;
	int b = 20;
	printf("a+b = %d\n", my_add(a, b));
	printf("a-b = %d\n", my_sub(a, b));
	printf("a*b = %d\n", my_mul(a, b));
	printf("a/b = %lf\n", my_div(a, b));

	return 0;
}

public.c

int my_add(int x, int y){
	return x+y;
}

int my_sub(int x, int y){
	return x-y;
}

int my_mul(int x, int y){
	return x*y;
}

double my_div(int x, int y){
	return (double)x/(double)y;
}

public.h

//防止头文件被一个项目中重复包含
#ifndef __PUBLIC_H__
#define __PUBLIC_H__

int my_add(int, int);
int my_sub(int, int);
int my_mul(int, int);
double my_div(int, int);

#endif

练习:

自己实现 my_strlen my_strcpy my_strcat my_strcmp 函数的功能,

my_string.h my_string.c

要求分文件编程,在 main.c 中调用并测试。

main.c

#include <stdio.h>
#include "my_string.h"

int main(int argc, const char *argv[])
{
	char s1[32] = "hello";
	char s2[32] = "beijing";

	//复制
	my_strcpy(s1, s2);
	printf("s1 = [%s]\n", s1);//beijing
	printf("s2 = [%s]\n", s2);//beijing
	
	//拼接
	my_strcat(s1, s2);
	printf("s1 = [%s]\n", s1);//beijingbeijing
	printf("s2 = [%s]\n", s2);//beijing

	//求长度
	int len = my_strlen(s1);
	printf("len = %d\n", len);//14

	//比较
	int ret = my_strcmp(s1, s2);
	printf("ret = %d\n", ret);//98

	return 0;
}

my_string.c

//函数的定义

//字符串的拷贝函数
char *my_strcpy(char *dest, const char *src){
	char *temp = dest;
	while(*src != '\0'){
		*temp++ = *src++;
	}
	*temp = *src;
	return dest;
}

//求长度
int my_strlen(const char *s){
	int count = 0;
	while(*s != '\0'){
		count++;
		s++;
	}
	return count;
}

//拼接
char *my_strcat(char *dest, const char *src){
	char *temp = dest;
	while(*temp != '\0'){
		temp++;
	}
	while(*src != '\0'){
		*temp++ = *src++;
	}
	*temp = *src;
	return dest;
}

//比较
int my_strcmp(const char *s1, const char *s2){
	while(*s1 != '\0' && *s2 != '\0'){
		if(*s1 != *s2){//说明有大小关系了
			return *s1 - *s2;	
		}
		s1++;
		s2++;
	}
	return *s1 - *s2;	
}

my_string.h

#ifndef __MY_STRING_H__
#define __MY_STRING_H__

char *my_strcpy(char *dest, const char *src);
int my_strlen(const char *s);
char *my_strcat(char *dest, const char *src);
int my_strcmp(const char *s1, const char *s2);

#endif

二、C语言的本质

操作内存。

三、分配内存的方式

3.1 由操作系统在栈区分配

定义变量的时候,操作系统会根据变量的类型在栈区给变量分配对应大小的空间。

定义变量的格式

存储类型  数据类型  变量名;

数据类型:

基本类型

char 1字节 %c

short 2字节 %hd

int 4字节 %d

long (32位4字节 64位系统8字节) %ld

long long 8字节 %lld

float 4字节 %f

double 8字节 %lf

枚举类型

构造类型

数组

结构体

共用体

指针类型

char *p

int **q

空类型

void

void *

3.2 动态内存的分配和回收

函数说明:

//malloc函数说明
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆区手动分配空间
参数:size  要分配的空间的大小  单位 字节
返回值:
    成功 分配的空间的首地址
    失败 NULL

//free函数说明
#include <stdlib.h>
void free(void *ptr);
功能:释放由malloc分配的空间
参数:ptr 释放的空间的首地址

malloc分配的内存空间是在堆区的,堆区的内存操作系统不会负责回收

需要程序员自己记得不使用的时候要回收资源,否则就会造成内存泄漏

内存泄漏:就是指只分配内容,没有回收。

内存泄漏一般只发生在长时间提供服务的服务器程序上,

我们练习是的 a.out 运行一下就结束了,进程结束的时候,操作系统会回收进程的所有资源。

例:

#include <stdio.h>
#include <stdlib.h>

int main(int argc, const char *argv[])
{
	//malloc的返回值是 void * 类型 隐式强转成其他类型 不会报错
	//但是一般书写代码时  我们都写成 显式强转 方便代码的阅读
	//int *p = (int *)malloc(4);
	int *p = (int *)malloc(sizeof(int));//这样写也可以
 if(NULL == p){
     printf("内存分配失败..\n");
     return -1;
 }
	printf("*p = %d\n", *p); //malloc分配的空间里面也是随机值
	*p = 1314;
	printf("*p = %d\n", *p); //1314

	//int value = 520;
	//p = &value;//这里的语法没问题  但是 malloc分配的空间的首地址就丢了
				//就没法回收了  会造成内存泄漏

	//释放分配的内存的空间
	//切记:自己malloc的空间 要自己手动 free
	free(p);
	p = NULL; //防止野指针 因为free之后 内存被回收了 但是 p还保存的首地址呢

	return 0;
}

练习:

自己封装一个能在堆区分配5个int大小的数组空间的函数,名为 hqyj_malloc()

再封装也释放自己申请的空间的函数 hqyj_free()

方式1:可以通过返回值来返回分配的空间的首地址

#include <stdio.h>
#include <stdlib.h>

int *hqyj_malloc(){
	int *p = (int *)malloc(sizeof(int) * 5);
	if(NULL == p){
		return NULL;
	}
	return p;
}

int main(int argc, const char *argv[])
{
	int *q = hqyj_malloc();	
	*(q+3) = 1314;  //给自己分配的数组 赋值
	printf("%d\n", q[3]);//读取内存中的值 

	free(q);
	q = NULL;

	return 0;
}

方式2:通过地址传递的方式来给主函数的指针赋值

#include <stdio.h>
#include <stdlib.h>

//错误的用法
//这种方式 只是将分配的空间的首地址赋值给 形参 p了
//并没有赋值给 实参的 q
//且函数调用之后 p 占用空间被操作系统回收了 就没法释放分配的空间了
//内存泄漏了
int hqyj_malloc1(int *p){
	p = (int *)malloc(sizeof(int) * 5);
	if(NULL == p){
		return -1;
	}
	printf("p = %p\n", p);//指向了分配的空间的首地址了
	return 0;
}

int hqyj_malloc2(int **p){
	*p = (int *)malloc(sizeof(int) * 5);
	if(NULL == *p){
		return -1;
	}
	printf("*p = %p\n", *p);//指向了分配的空间的首地址了
	return 0;
}
#if 0
void hqyj_free1(int *p){
	free(p);//释放是可以的
	p = NULL;//p置成NULL了 但是 实参的q还是野指针呢
}
#endif

void hqyj_free2(int **p){
	free(*p);
	*p = NULL;
}

int main(int argc, const char *argv[])
{
	int *q = NULL;
	//hqyj_malloc1(q); //错误的
	//printf("q = %p\n", q);//NULL
	
	hqyj_malloc2(&q); //正确的
	printf("q = %p\n", q);//指向了分配的空间的首地址了
	q[0] = 520;
	printf("q[0] = %d\n", q[0]);//520

	//hqyj_free1(q);
	//q = NULL;
	
	hqyj_free2(&q);

	return 0;
}

四、存储类型

4.1 const

const 修饰变量的时候,表示修饰的是一个只读变量。

const int a = 10;

a = 20;//错误的

const 修饰指针的时候,注意下面用法

const int *p;

int const *p;

int * const p;

const int * const p;

4.2 static

static关键字有两个作用:

1.延长局部变量的生命周期(见例1)

2.限制作用域(例子见下面 extern 关键字的例子)

1.在局部定义变量叫做局部变量,局部变量在最近的{}结束时,生命周期就结束了

操作系统会回收局部变量占用的内存空间。如果加了static修饰,相当于修饰成了静态变量

静态变量被定义在 bss段和 data段,占用的内存空间在main函数执行之前就分配了。

且生命周期被延长至整个程序结束。

2.static修饰的变量只能在当前文件中访问

例1:

#include <stdio.h>

void func1(){
	int m = 10;
	m++;
	printf("m = %d\n", m);
}

void func2(){
	//相当于第一次调用时 该语句生效 以后再调用时 该语句就不生效了
	static int m = 10;
	m++;
	printf("m = %d\n", m);
}

int main(int argc, const char *argv[])
{
	func1();//11
	func1();//11
	func1();//11

	func2();//11
	func2();//12
	func2();//13

	//printf("m = %d\n", m);//即使被static修饰了 也还是局部变量
						//作用域之外也不能访问
	
	return 0;
}

4.3 extern

作用:声明变量或者函数是在其他的文件中定义的。

如果在一个.c文件中想使用其他.c文件的变量或者函数

需要在第一个.c文件中使用 extern 来做声明。

例:

main.c

#include <stdio.h>

//生命 函数 my_add 和 变量 value1 是在其他的 .c 文件中定义的
extern int my_add(int x, int y);
extern int value1;

//extern int value2; //static修饰的变量只能在其自己的文件中访问

int main(int argc, const char *argv[])
{
	int ret = my_add(10, 20);
	printf("ret = %d\n", ret);//30

	printf("main : value1 = %d\n", value1);//1314
	//printf("main : value2 = %d\n", value2);

	return 0;
}

public.c

#include <stdio.h>

int value1 = 1314;
static int value2 = 520;//static修饰的变量只能在当前文件中访问

int my_add(int x, int y){
    printf("my_add : value1 = %d\n", value1);//1314
    printf("my_add : value2 = %d\n", value2);//520
    return x+y;
}

4.4 register

修饰的是一个寄存器类型的变量

寄存器类型的变量,访问的效率更高。

CPU访问数据的优先级 : 寄存器 >> cache(告诉缓存) >> 内存

因为CPU寄存器的个数是有限的,所以我们不能把所有的变量都定义成register类型的

一般情况下,应用层开发基本用不到 register

如果遇到了,注意:register修饰的变量不能取地址

#include <stdio.h>

int main(int argc, const char *argv[])
{
	register int a = 10;
	printf("a = %d\n", a);//10
	
	//int *p = &a;//会报错

	return 0;
}

4.5 volatile

volatile关键修饰的变量表示 防止编译器优化,

要求每次取数据,必须在内存上取,而不是在寄存器或者cache中取。

volatile的使用场景:

1.多线程访问同一个变量的时候

2.访问中断状态的寄存器

4.6 auto

声明的变量是一个自动类型的变量。

局部变量不写存储类型默认的就是 auto。

非自动类型的变量:

1.全局变量

2.static修饰的局部变量

五、枚举类型

5.1 概念

枚举是数据的有限罗列,是一个基本类型。

枚举可以用来防魔鬼数字。

5.2 定义枚举类型的格式

enum 枚举类型名{
    成员1,
    成员2,
    成员3 = 100,
    成员4,
    成员n
};
//枚举类型名 也是一个标识符 需要符合标识符的命名规范

5.3 枚举类型的基本使用

#include <stdio.h>

enum Color{
	red,
	white,
	blue,
	green = 100,
	yellow,
	pink,
	black
};

int main(int argc, const char *argv[])
{
	//使用枚举类型 定义变量
	enum Color value1;
	//枚举的成员可以用来给枚举变量赋值
	value1 = white;
	printf("value1 = %d\n", value1);//1

	//初始化的写法
	enum Color value2 = yellow;
	printf("value2 = %d\n", value2);//101

	//一行也可以定义多个变量
	enum Color value3 = pink, value4 = red;
	printf("value3 = %d\n", value3);//102
	printf("value4 = %d\n", value4);//0

	//枚举的变量之间可以直接相互赋值
	value3 = value4;
	printf("value3 = %d\n", value3);//0

	//也可以使用整数来给枚举变量赋值
	//但是这样做就失去了枚举的意义了
	value3 = 78;
	printf("value3 = %d\n", value3);//78

	//枚举一旦定义好之后 成员就是常量
	//yellow = 520;//错误的
	
	//枚举成员和局部变量名字冲突时 采用局部优先原则
	int yellow = 123;
	value3 = yellow;
	printf("value3 = %d\n", value3);//123

	//枚举类型的大小
	printf("sizeof(enum Color) = %ld\n", sizeof(enum Color));//4
	printf("sizeof(value1) = %ld\n", sizeof(value1));//4
	
	return 0;
}

5.4 注意事项

1.枚举一旦定义好之后,成员都是常量

2.枚举的成员1如果没有初始值,默认是0

3.枚举成员的值是依次递增的,依次加1

4.如果给某个成员赋了初始值,就从这个值开始依次自增

5.枚举的成员之间使用 逗号 分隔

6.如果枚举成员的名字和局部变量的名字冲突,采用局部优先原则

7.枚举类型的大小:一般情况下都是4字节,如果有成员超过了4字节的范围,就是8

5.5 枚举类型定义变量的格式

1. enum 枚举类型名  枚举变量名;

2. 在定义枚举类型的同时 定义枚举变量
enum Color{
    red, green, blue
}c1, c2;

3.省略枚举类型名的方式定义枚举变量 --这种方式就没法再定义其他的变量了
enum{
    red, green, blue
}c1, c2;

例:

#include <stdio.h>

enum Color{
	red, green, blue
}c1, c2;

enum {//--这种方式就没法再定义其他的变量了
	boy, girl
}sex;

int main(int argc, const char *argv[])
{
	c1 = green;
	c2 = blue;
	printf("c1 = %d, c2 = %d\n", c1, c2);//1 ,2
	enum Color c3 = blue;
	printf("c3 = %d\n", c3);//2

	sex = boy;
	printf("sex = %d\n", sex);//0

	return 0;
}

5.6 枚举和typedef的结合使用

写法1:
typedef enum Color{
    red, green, blue
}hqyj;//这种写法既可以使用hqyj定义变量,也可以使用 enum Color 定义变量
    //hqyj本来是枚举变量名,加了typedef之后
    //就变成新的枚举类型名
    //使用hqyj 定义的变量和使用 enum Color 定义的变量是一样的
写法2:
typedef enum{
    boy, girl
}sex;//这种写法只能使用  sex 定义变量

例:

#include <stdio.h>

typedef enum Color{
	red, green, blue
}hqyj1;

typedef enum{
	boy, girl
}sex;

int main(int argc, const char *argv[])
{
	hqyj1 c1 = green;
	printf("c1 = %d\n", c1);//1

	sex s1 = girl;
	printf("s1 = %d\n", s1);//1

	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值