目录标题
动态内存管理
通过创建变量的方式来申请内存的.啥时候释放内存,就得看变量是啥样的变量了.
如果是全局变量,就跟随程序释放.
如果是静态变量,也跟随程序释放.
如果是局部变量,就跟随代码块释放.
1.动态内存管理,能够更灵活的决定申请时机和释放时机~
2.动态内存管理可以在运行时决定内存申请的大小~
malloc
void* malloc (size_t size);
//size字节数,编译器只知道内存起始地址,
//不知道大小,申请连续内存,
//正常得情况下要检查是否申请成功(不成功返回NULL)
使用
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
int* p = (int*)malloc(4);
*p = 10;
printf("%d\n", *p);
//把这 40 个字节理解成一个 长度 10 个元素的 int 数组
// p 就当成了数组的起始元素地址.
int* p = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) {
p[i] = 0;
}
system("pause");
return 0;
}
释放时间
1.程序如果运行结束,就跟随程序一起释放了.
2.程序没结束的时候,手动调用free ,也就释放了.
free
void free (void* ptr);//ptr maolloc返回的地址,可以处理NULL
使用
#include<Windows.h>
#include<stdio.h>
#include<stdlib.h>
int main(){
//把这 40 个字节理解成一个 长度 10 个元素的 int 数组
// p 就当成了数组的起始元素地址.
int* p = (int*)malloc(10 * sizeof(int));
for (int i = 0; i < 10; i++) {
p[i] = 0;
}
free(p);
system("pause");
return 0;
}
在C++里面,引入了"智能指针”,就能一定程度的解决内存泄露问题.利用了C++中的RAlI机制~就能够在函数退出之前,执行一些逻辑.
就可以把free操作放到这样的逻辑之中~
Java / Python,引入了垃圾回收机制,就能更好的解决内存泄露问题.
内存的回收不必由程序猿手动回收了,而是程序内部,有专门的逻辑来定期扫描,看看当前哪些内存是可以释放然后就自动释放了.
calloc
void* calloc (size_t num, size_t size);
使用
#include <stdio.h>
#include <stdlib.h>
int main()
{
int *p = (int*)calloc(10, sizeof(int));
if(NULL != p)
{
//使用空间
}
free(p);
p = NULL;
return 0; }
calloc申请到的内存,会被初始化为全0.
malloc申请到的内存,则不会初始化,内存上的值都是随机值.
realloc
realloc扩容的时候,也是看后面的空余空间够不够~如果后面的空间够,就直接把墙打通,直接利用后面的空间.
如果后面的空间不够,就直接找一个更大的空间,整体搬运过去~
void* realloc (void* ptr, size_t size);
使用
#include <stdio.h>
int main()
{
int *ptr = (int*)malloc(100);
if(ptr != NULL)
{
//业务处理
}
else
{
exit(EXIT_FAILURE);
}
//扩展容量
//代码1
ptr = (int*)realloc(ptr, 1000);//这样可以吗?(如果申请失败会如何?)
//代码2
int*p = NULL;
p = realloc(ptr, 1000);
if(p != NULL)
{
ptr = p;
}
//业务处理
free(ptr);
return 0; }
常见的内存错误
void test()
{
int *p = (int *)malloc(INT_MAX/4);
*p = 20;//如果p的值是NULL,就会有问题
free(p);
}
void test()
{
int i = 0;
int *p = (int *)malloc(10*sizeof(int));
if(NULL == p)
{
exit(EXIT_FAILURE);
}
for(i=0; i<=10; i++)
{
*(p+i) = i;//当i是10的时候越界访问
}
free(p);
}
void test()
{
int a = 10;
int *p = &a;
free(p);//只释放动态申请的内存
}
void test()
{
int *p = (int *)malloc(100);
p++;
free(p);//p不再指向动态内存的起始位置
}
void test()
{
int *p = (int *)malloc(100);
free(p);
free(p);//重复释放
}
void test()
{
int *p = (int *)malloc(100);
if(NULL != p)
{
*p = 20;
}//内存泄漏
}
int main()
{
test();
while(1);
}
常见的题型
1.
void GetMemory(char *p) {
p = (char *)malloc(100);
}
void Test(void) {
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
1.内存泄露. malloc没有free
2. malloc之后没有判定返回值为NULL
3.GetMemory函数并不能修改str.的值~str 仍然是 NULL.后面进行strcpy,就会出现问题~
2.
char *GetMemory(void) {
char p[] = "hello world";//局部变量随着函数释放而释放
return p; }
void Test(void) {
char *str = NULL;
str = GetMemory();//局部变量所指的内存已经释放
printf(str);
}
3.
void GetMemory(char **p, int num) {
*p = (char *)malloc(num);
}
void Test(void) {
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
1.内存泄露. malloc没有free
2. malloc之后没有判定返回值为NULL
4.
void Test(void) {
char *str = (char *) malloc(100);
strcpy(str, "hello");
free(str);
if(str != NULL)
{
strcpy(str, "world");
printf(str);
}
}
一个程序的内存区域
linux中
栈的大小可以配置(一般1m)
linux修改栈的大小
文件
分类
普通文件:文本文件,二进制文件
目录文件
相关函数
io函数都在stdio.h中包含
FILE结构体
用来描述一个文件~
文件是在磁盘上的.要想直接操作磁盘,不太容易.因此操作系统就进行了封装.
打开文件的时候,其实就是在内存中创建了一个变量(FILE结构体变量),这个变量就和磁盘上的文件关联起来了.读/写这个变量,也就相当于在读写文件了.
fopen
//打开文件
FILE * fopen ( const char * filename, const char * mode );
//参数前者文件路径,后者打开方式
使用
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
/* fp => file pointer 文件指针
如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/
FILE* fp = fopen("E:/test.txt", "r");
if (fp == NULL) {
printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
system("pause");
return 0;
}
printf("打开文件成功! fp=%p\n", fp);
system("pause");
return 0;
}
strerror
返回错误码,所对应的错误信息。
char * strerror ( int errnum );
使用
#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件
int main ()
{
FILE * pFile;
pFile = fopen ("unexist.ent","r");
if (pFile == NULL)
printf ("Error opening file unexist.ent: %s\n",strerror(errno));
//errno: Last error number
return 0; }
fclose
//关闭文件
int fclose ( FILE * stream );
使用
#include <stdio.h>
int main ()
{
FILE * pFile;
//打开文件
pFile = fopen ("myfile.txt","w");
//文件操作
if (pFile!=NULL)
{
fputs ("fopen example",pFile);
//关闭文件
fclose (pFile);
}
return 0; }
fread
数据读取方式:字节流和数据报
size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
//*ptr读到内容的内存地址,返回值读取成功的个数
使用
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
/* fp => file pointer 文件指针
如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/
FILE* fp = fopen("E:/test.txt", "r");
if (fp == NULL) {
printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
system("pause");
return 0;
}
printf("打开文件成功! fp=%p\n", fp);
char buffer[10] = "1111" ;
fread(buffer, 1, 8, fp);
printf(buffer);
system("pause");
return 0;
}
fwrite
size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
使用
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
/* fp => file pointer 文件指针
如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/
FILE* fp = fopen("E:/test.txt", "w");
if (fp == NULL) {
printf("打开文件失败! 错误码: %s\n", strerror(errno));//error一个宏定义代表错误
system("pause");
return 0;
}
printf("打开文件成功! fp=%p\n", fp);
char buffer[10] = "1111" ;
fwrite(buffer, 1, 4, fp);
printf(buffer);
system("pause");
return 0;
}
其他顺序读写
EOF
如果一个文件已经被读完了,然后再来继续尝试读,就会读到EOF.
EOF '实际上是-1,并不是一个ascii码表中的字符.只是使用-1这个数据来代表文件读取完毕.
sscanf和sprintf
//sscanf从一个字符串里解析出一个内容.
//sprintf 把一个格式化结果输出到一个字符串中.
char* str = "num = 10";
int num = 0;
sscanf(str, "num = %d", &num);
printf("%d\n", num);
char str[1024] = { 0 };
sprintf(str, "num = %d", 10);
printf(str);
一个特别重要的用法://针对字符串和数字(整数/浮点数)之间进行转换~
// 把字符串转成整数, 使用 sscanf
char* str1 = "10";
char* str2 = "20";
// 此处想计算这两个值相加~
int num1 = 0;
// 这个时候就是把 str1 中的内容提取出来, 转成了 int
sscanf(str1, "%d", &num1);
int num2 = 0;
sscanf(str2, "%d", &num2);
int result = num1 + num2;
printf("result = %d\n", result);
// 把整数转成字符串, 使用 sprintf
int num = 100;
char buffer[1024] = { 0 };
sprintf(buffer, "%d", num);
printf("%s\n", buffer);
整型和字符串的转换 atoi iota
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> /* atoi */
int main ()
{
int i;
char buffer[256];
printf("Enter a number: ");
fgets(buffer, 256, stdin);
i = atoi(buffer);
printf("The value entered is %d. Its double is %d.\n", i, i * 2);
system("pause");
return 0;
}
char * itoa ( int value, char * str, int base );
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h> /* atoi */
int main ()
{
int i;
char buffer[33];
printf("Enter a number: ");
scanf("%d", &i);
_itoa(i, buffer, 10);
printf("decimal: %s\n", buffer);
_itoa(i, buffer, 16);
printf("hexadecimal: %s\n", buffer);
_itoa(i, buffer, 2);
printf("binary: %s\n", buffer);
system("pause");
return 0;
}
fscanf
int fscanf ( FILE * stream, const char * format, ... );
使用
/* fscanf example */
#include <stdio.h>
int main ()
{
char str [80];
float f;
FILE * pFile;
pFile = fopen ("myfile.txt","w+");
fprintf (pFile, "%f %s", 3.1416, "PI");
rewind (pFile);
fscanf (pFile, "%f", &f);
fscanf (pFile, "%s", str);
fclose (pFile);
printf ("I have read: %f and %s \n",f,str);
return 0;
}
fprintf
int fprintf ( FILE * stream, const char * format, ... );
使用
#define _CRT_SECURE_NO_WARNINGS
#include<Windows.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
int main(){
/* fp => file pointer 文件指针
如果打开文件失败, 就会返回 NULL. 如果文件不存在或者没有读写权限, 就会失败.*/
FILE* fp = fopen("E:/test.txt", "w");
if (fp == NULL) {
printf("打开文件失败! 错误码: %s\n", strerror(errno));//errno一个宏定义代表错误
system("pause");
return 0;
}
printf("打开文件成功! fp=%p\n", fp);
char buffer[10] = "1111" ;
fprintf(fp,"jiezhe:%s",buffer);
printf(buffer);
system("pause");
return 0;
}
// stdout 其实就是一个 FILE* 类型的变量. 这个就是咱们所说的标准输出.
// 这个代码就等价于 printf
fprintf(stdout, "num = %d\n", 10);
// 类似的, fscanf(stdin, "xxxxx"); 这个代码就等价于 scanf
随机读写
fseek
根据文件指针的位置和偏移量来定位文件指针
int fseek ( FILE * stream, long int offset, int origin );
#include <stdio.h>
int main ()
{
FILE * pFile;
pFile = fopen ( "example.txt" , "wb" );
fputs ( "This is an apple." , pFile );
fseek ( pFile , 9 , SEEK_SET );
fputs ( " sam" , pFile );
fclose ( pFile );
return 0;
}
ftell
返回文件指针相对于起始位置的偏移量
long int ftell ( FILE * stream );
#include <stdio.h>
int main ()
{
FILE * pFile;
long size;
pFile = fopen ("myfile.txt","rb");
if (pFile==NULL) perror ("Error opening file");
else
{
fseek (pFile, 0, SEEK_END); // non-portable
size=ftell (pFile);
fclose (pFile);
printf ("Size of myfile.txt: %ld bytes.\n",size);
}
return 0; }
rewind
让文件指针的位置回到文件的起始位置
void rewind ( FILE * stream );
#include <stdio.h>
int main ()
{
int n;
FILE * pFile;
char buffer [27];
pFile = fopen ("myfile.txt","w+");
for ( n='A' ; n<='Z' ; n++)
fputc ( n, pFile);
rewind (pFile);
fread (buffer,1,26,pFile);
fclose (pFile);
buffer[26]='\0';
puts (buffer);
return 0; }
文件读取结束的判定
- 文本文件读取是否结束,判断返回值是否为 EOF ( fgetc ),或者 NULL ( fgets )
例如:
fgetc 判断是否为 EOF .
fgets 判断返回值是否为 NULL . - 二进制文件的读取结束判断,判断返回值是否小于实际要读的个数。
例如:
fread判断返回值是否小于实际要读的个数。
刷新缓存区
#include <stdio.h>
#include <windows.h>
//VS2013 WIN10环境测试
int main()
{
FILE*pf = fopen("test.txt", "w");
fputs("abcdef", pf);//先将代码放在输出缓冲区
printf("睡眠10秒-已经写数据了,打开test.txt文件,发现文件没有内容\n");
Sleep(10000);
printf("刷新缓冲区\n");
fflush(pf);//刷新缓冲区时,才将输出缓冲区的数据写到文件(磁盘)
//注:fflush 在高版本的VS上不能使用了
printf("再睡眠10秒-此时,再次打开test.txt文件,文件有内容了\n");
Sleep(10000);
fclose(pf);
//注:fclose在关闭文件的时候,也会刷新缓冲区
pf = NULL;
return 0; }
刷新缓存区时间:
1.缓冲区满了.
2. fclose关闭了.
3. fflush这个函数的功能就是手动刷新缓冲区.
预处理
2编译过程最重要
预定义符号
__FILE__ //进行编译的源文件
__LINE__ //文件当前的行号
__DATE__ //文件被编译的日期
__TIME__ //文件被编译的时间
__STDC__ //如果编译器遵循ANSI C,其值为1,否则未定义
define
1.定义常量~
#define MAX 1000
2.给类型定义别名~
#define uint unsigned int
uint num = 10;
3.自定义一些"关键字"|
#define and &&
#define 并且 &&
#define 如果 if
#define 否则 else
#define 循环 for
#define 赋值 =
#define 整型 int
int num = 10;
if (num < 10 并且 num > 0) {
}
整型 num 赋值 10;
如果(num < 10 并且 num > 0) {
} 否则 {
}
4.通过宏作为一些"编译开关”(结合者#if这系列操f作)
5.定义一个代码片段.
#define ADD(x, y) x + y
printf("%d\n", ADD(10, 20));
宏优势和问题
3.宏不可递归
4.宏没有参数检查.(参数不写类型,看起来好像是方便了,但实际上反而是更麻烦了.参数没有检查了,你实际参数传的对不对,是否符合要求,都不得而知了)|
#undef
这条指令用于移除一个宏定义。
条件编译
1.
#if 常量表达式
//...
#endif
//常量表达式由预处理器求值。
如:
#define __DEBUG__ 1
#if __DEBUG__
//..
#endif
2.多个分支的条件编译
#if 常量表达式
//...
#elif 常量表达式
//...
#else
//...
#endif
3.判断是否被定义
#if defined(symbol)
#ifdef symbol
#if !defined(symbol)
#ifndef symbol
4.嵌套指令
#if defined(OS_UNIX)
#ifdef OPTION1
unix_version_option1();
#endif
#ifdef OPTION2
unix_version_option2();
#endif
#elif defined(OS_MSDOS)
#ifdef OPTION2
msdos_version_option2();
#endif
#endif
应用:
1.兼容开发和发布环境,
2.兼容多系统,
3.防止头文件重复包含(#pragma once 在定义处使用),
4.实现多行注释,如下
5.支持嵌套