分文件编程、分配内存的方式、存储类型、枚举类型
一、分文件编程
实际开发过程中,会根据函数的功能不同,按照模块,将函数拆分成多个文件来处理。
.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;
}