C语言提高学习笔记

C语言提高

1.数据类型

每个数据必须指定类型,由编译器创建的,为了更好的管理内存

  1. 基本类型
    1. 整型
    2. 字符型
    3. 浮点型
      1. 单精度
      2. 双精度
  2. 构造类型
    1. 数组类型
    2. 结构体
    3. 联合体
    4. 枚举
  3. 指针类型

2. typedef使用

  • 起别名 - 简化struct关键字

  • 区分数据类型

  • 提高代码移植性

3. void使用

不可以利用void创建变量 无法给无类型变量分配内存

用途:

  • 限定函数返回值,函数参数

  • void * 万能指针 可以不通过强制类型转换就转成其他类型指针

4. sizeof用法

本质:不是一个函数,是一个操作符

返回值类型 unsigned int无符号整型

用途:可以统计数组长度

数组当参数传递后使用当指针使用,存放第一个数组元素地址

5. 变量的修改方式

  • 直接修改

  • 间接修改:指针

6. 内存分区

  1. 运行前

    1. 代码区:只读,共享
    2. 数据区:存放数据:全局变量 、静态变量、常量
      1. 已初始化数据区 data:已初始化全局变量、静态变量(全局和局部)、常量数据。
      2. 未初始化数据区 bss :未初始化的全局变量和静态变量。
  2. 运行后、

    1. 栈 符合先进后出数据结构,编译器自动管理分配和释放,有限容量

      不要返回局部变量的地址,局部变量在函数执行之后就被释放了,释放的内存就没有权限取操作了,如果操作结果未知

    2. 堆 容量远远大于栈,不是无限。手动开辟 malloc 手动释放 free

      主调函数没有分配内存,被调函数需要用更高级的指针去修饰低级指针,进行分配内存

7. static和extern区别

  • static静态变量:在程序运行前分配内存,程序运行结束生命周期结束,在本文件内都可以使用静态变量,作用域有限制
  • extern外部变量:表明变量在其他文件中,编译器在全局变量会隐式默认加

8. 常量

  • const修饰的变量:分为全局和局部变量,
    • 全局变量。直接修改编译器报错,间接修改语法通过,运行失败。原因:收到常量区保护。
    • 局部变量。直接修改报错。间接修改成功,称为伪常量。不可以用来初始化数组。
  • 字符串常量:char *p="helloworld",内容相同,地址相同。由编译器决定。不可以修改字符串常量。ANSI并没有制定出字符串是否可以修改的标准,根据编译器不同,可能最终结果也是不同的

9. 函数调用流程

  • 宏函数
    • 宏函数需要加小括号修饰,保证运算完整性
    • 通常将频繁使用,短小的函数写成宏函数
    • 一定程度上比普通函数效率高,省去普通函数入栈、出栈的时间(以空间换时间)

局部变量、函数形参、函数返回地址. 入栈 和 出栈

调用惯例:vs默认cdecl

主调函数和被调函数必须要有一致约定,才能正确的调用函数,这个约定我们称为调用惯例

调用惯例 包含内容: 出栈方、参数传递顺序、函数名称修饰

C/C++下默认调用惯例: cdecl 从右到左 ,主调函数管理出栈 变量名前加下划线修饰

//变量传递分析
char * func()
{
	char * p =  malloc(10); //堆区数据,只要没有释放,都可以使用
	int c = 10;//在func中可以使用,test01和main都不可以使用
	return p;
}

void test01()
{
	int b = 10; // 在test01 、func 可以使用,在main中不可以用

	func();
}

int main(){

	int a = 10; //在main 、test01 、 func中都可以使用
	test01();
	system("pause");
	return EXIT_SUCCESS;
}

10. 栈的生长方向和内存存放方向

栈生长方向

栈底 — 高地址

栈顶 — 低地址

内存存放方向

高位字节数据 — 高地址

低位字节数据 — 低地址

小端对齐方式

void test01()
{
	int a = 10;  //栈底  高地址
	int b = 10;
	int c = 10;
	int d = 10;  //栈顶  低地址


	printf("%d\n", &a);
	printf("%d\n", &b);
	printf("%d\n", &c);
	printf("%d\n", &d);

}


//2、内存存放方向
void test02()
{
	int a = 0x11223344;

	char * p = &a;

	printf("%x\n", *p);    //44  低位字节数据  低地址
	printf("%x\n", *(p+1)); //33  高位字节数据  高地址
}

int main(){


	//test01();
	test02();
	system("pause");
	return EXIT_SUCCESS;
}

11. 空指针和野指针

  • 空指针

    不能向NULL或者非法内存拷贝数据

  • 野指针

    指针变量未初始化

    指针释放后未置空

    指针操作超越变量作用域

  • 空指针可以重复释放、野指针不可以重复释放

//1、不能向NULL或者非法内存拷贝数据
void test01()
{
	//char *p = NULL;
	给p指向的内存区域拷贝内容
	//strcpy(p, "1111"); //err
 
	//char *q = 0x1122;
	给q指向的内存区域拷贝内容
	//strcpy(q, "2222"); //err		

}


//指针操作超越变量作用域
int * doWork()
{
	int a = 10;
	int * p = &a;
	return p;
}

//2、野指针出现情况
void test02()
{
	//2.1 指针变量未初始化
	/*int * p;
	printf("%d\n",*p);*/



	//2.2 指针释放后未置空
	char * str  = malloc(100);
	free(str);
	//记住释放后 置空,防止野指针出现
	//str = NULL;

	//free(str);
	//2.3 空指针可以重复释放、野指针不可以重复释放

	//2.4 指针操作超越变量作用域
	int * p = doWork();
	printf("%d\n", *p);
	printf("%d\n", *p);
}

12. 指针的步长

  • +1之后跳跃的字节数

  • 解引用 解出的字节数

  • 自定义结构体做步长练习

通过 offsetof( 结构体名称, 属性) 找到属性对应的偏移量

offsetof 引入头文件 #include<stddef.h>

//1、指针的步长代表 指针+1之后跳跃的字节数
void test01()
{
	char * p = NULL;
	printf("%d\n", p);
	printf("%d\n", p+1);


	double * p2 = NULL;
	printf("%d\n", p2);
	printf("%d\n", p2 + 1);

}

//2、解引用的时候,解出的字节数量
void test02()
{
	char buf[1024] = { 0 };

	int a = 1000;

	memcpy(buf + 1, &a, sizeof(int));

	char * p = buf;
	printf("%d\n", *(int *)(p+1));

}

//步长练习,自定义数据类型练习
struct Person
{
	char a;     // 0 ~ 3
	int b;      // 4 ~ 7
	char buf[64]; // 8 ~ 71
	int d;     // 72 ~ 75
};

void test03()
{
	struct Person p = { 'a', 10, "hello world", 20 };
	
	printf("d属性的偏移量: %d\n", offsetof(struct Person, d));

	printf("d属性的值为:%d\n", *(int *)((char *)&p + offsetof(struct Person, d)));
}

13. 指针的间接赋值

  • 三大条件

    • 一个普通变量+指针变量( 实参+形参)

    • 建立关系

    • 通过* 操作内存

利用Qt实现 操作地址 修改内存

14. 指针做函数参数的输入输出特性

  • 输入特性

    在主调函数中分配内存,被调函数使用

    分配在栈上和堆区

  • 输出特性

    在被调函数中分配内存,主调函数使用

15.字符串强化训练

  • 字符串结束标志 \0

  • sizeof 和 strlen

  1. 拷贝字符串 利用三种方式

    利用[]

    利用指针

    while (*dest++ = *src++){}

  2. 翻转字符串

    利用[ ]

    利用指针

  3. sprintf使用

    格式化字符串

    sprintf(目标字符串,格式化内容,占位参数…)

    返回值 有效字符串长度

16. calloc 和 realloc

  • calloc 和malloc 都是在堆区分配内存
  1. 与malloc不同的是,calloc会将空间初始化为0

  2. calloc(个数,大小)colloc(10,sizeof(int))=malloc(sizeof(int)*10),成功返回空间起始地址,失败发挥null

  • realloc 重新分配内存
  1. 如果重新分配的内存比原来大,那么不会初始化新空间为0
  2. 先看后续空间,如果足够,那么直接扩展
  3. 如果后续空闲空间不足,那么申请足够大的空间,将原有数据拷贝到新空间下,释放掉原有空间,将新空间的首地址返回
  4. 如果重新分配的内存比原来小,那么释放后序空间,只有权限操作申请空间

17. scanf()使用

%*s或%*d跳过数据
%[width]s读指定宽度的数据
%[a-z]匹配a到z中任意字符(尽可能多的匹配)
%[aBc]匹配a、B、c中一员,贪婪性
%[^a]匹配非a的任意字符,贪婪性
%[^a-z]表示读取除a-z以外的所有字符

18.查找子串

实现mystrstr 自己查找子串功能mystrstr

int myStrstr(char *str, char * substr)
{
	int n = 0;
	while (*str != '\0') 
	{
		if (*str != *substr) 
		{
			n++;
			str++;
			substr++;
		}
		char *tmpStr = str;
		char *tmpSubStr = substr;
		while (*tmpSubStr != '\0') 
		{
			if (*tmpStr != *tmpSubStr)
			{
				n++;
				str++;
				break;
			}
			tmpStr++;
			tmpSubStr++;

		}
		if (*tmpSubStr == '\0')
		{
			return n;
		}
	}
	return -1;
}

void test01()
{
	char * str = "abdnfcdefgdfasdfaf";

	int ret = myStrstr(str, "dnf");

	if (ret != -1)
	{
		printf("找到了子串,位置为:%d\n", ret);
	}
	else
	{
		printf("未找到子串\n");
	}

}

19.指针的易错点

  • 越界
  • 指针叠加会不断改变指针指向
  • 返回局部变量地址
  • 同一块内存释放多次(不可以释放野指针)

20.二级指针做函数参数的输入输出特性

  • 二级指针做函数参数的输入特性

    • 创建在堆区
    • 创建在栈区
  • 二级指针做函数参数的输出特性

    • 被调函数分配内存,主调函数使用

21. const 修饰形参,防止误操作

22. 二级指针文件操作练习

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int  getGetFileLines(FILE *fp)
{
	if (fp == NULL)
	{
		return -1;
	}
	char buf[1024] = { 0 };
	int lines = 0;
	while (fgets(buf,1024,fp)!=NULL)
	{
		lines++;
	}
	fseek(fp, 0, SEEK_SET);
	return lines;
}

void readFileData(FILE *fp, int len, char **pArray)
{
	if (fp == NULL)
	{
		return;
	}
	if (len <= 0)
	{
		return;
	}
	if (pArray ==NULL)
	{
		return;
	}
	char buf[1024] = { 0 };
	int i = 0;
	while (fgets(buf, 1024, fp) != NULL)
	{
		int curLen = strlen(buf) + 1;
		char * curStr = malloc(curLen);
		strcpy(curStr, buf);
		pArray[i++] = curStr;
		memset(buf, 0, 1024);
	}
}
void showFileData(char ** pArray, int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%s", pArray[i]);
	}
}

void test01()
{
	FILE *fp = fopen("./test.txt", "r+");
	if (fp == NULL)
	{
		printf("文件打开失败");
		return;
	}
	int len = getGetFileLines(fp);
	printf("%d\n", len);
	char **pArray = malloc(sizeof(char *)* 5);
	readFileData(fp, len, pArray);
	showFileData(pArray, len);

	//释放堆区内容
	for (int i = 0; i < len; i++)
	{
		if (pArray[i] != NULL)
		{
			free(pArray[i]);
			pArray[i] = NULL;
		}
	}
	free(pArray);
	pArray = NULL;
	//关闭文件
	fclose(fp);
}

23. 位运算

按位取反 ~ 0变1 1 变0

按位与 & 全1为1 一0为0

按位或 | 全0为0 一1为1

按位异或 ^ 相同为0 不同为1

24. 位移运算

左移运算 << X 乘以2 ^ X

右移运算 >> X 除以 2 ^X

有些机器用0填充高位

有些机器用1填充高位

如果是无符号,都是用0填充

25. 一维数组名

  • 除了两种特殊情况外,都是指向数组第一个元素的指针

    1. sizeof 统计数组长度int arr[5]={1,2,3,4,5} sizeof(arr)为20
    2. 对数组名取地址, &arr数组指针,步长整个数组长度

    指针步长:offset(struct Person,d) 头文件<stddef.h>

数组名是指针常量,指针的指向不可以修改的,而指针指向的值可以改

传参数时候,int arr[] 可读性更高int arr[] 等价于 int * arr

数组索引下标可以为负数

int * const a指针常量 :a是常量,指针的常量,它是不可改变地址的指针,但是可以对它所指向的内容进行修改。

const int * a 常量指针 :*a是常量,指向常量的指针,指针所指向的地址的内容是不可修改的

26. 数组指针的定义方式

  1. 先定义出数组类型,再通过类型定义数组指针变量

    int arr[5] = {1,2,3,4,5};
    typedef int(ARRARY_TYPE)[5];//ARRARY_TYPE
    ARRARY_TYPE * arrP = &arr;
    //*arrP = arr = 数组名
    

    代表存放5个int类型元素的数组 的数组类型

  2. 先定义数组指针类型,再通过类型定义数组指针变量

    typedef int(*ARRARY_TYPE)[5];
    ARRARY_TYPE  arrP = &arr;
    
  3. 直接定义数组指针变量

    int(* p )[5] = &arr; // *p 等于数组名

27. 二维数组名

  • 二维数组名 除了两种特殊情况外,是指向第一个一维数组的 数组指针
  1. sizeof 统计二维数组大小
  2. 对数组名称取地址 int(*p)[3][3] = &arr
  • 二维数组做函数参数

    • void printArray(int (*array)[3], int row, int col)
    • void printArray(int array[][3], int row ,int col)
    • void printArray(int array[3][3], int row ,int col) 可读性比较高
  • 数组指针 和 指针数组?

    • 数组指针: 指向数组的指针
    • 指针数组: 由指针组成数组

28. 指针数组排序

void sortArray(char **pArray,int len)
{
	int min=0;
	int i = 0, j =0;
	for (i = 0; i < len; i++)
	{
		min = i;
		for (j = i + 1; j < len; j++)
		{
			if (strcmp(pArray[j],pArray[min]) == -1)
			{
				min = j;
			}
		}
		if (i != min)
		{
			char * tmp="";
			
			tmp = pArray[i];
			pArray[i] = pArray[min];
			pArray[min] = tmp;
		}
	}	
}

29. 结构体基本概念

  • 加typedef 可以给结构体起别名

  • 不加typedef ,可以直接创建一个结构体变量

  • 结构体声明 可以是匿名

  • 在栈上创建和在堆区创建结构体

  • 在栈上和堆区创建结构体变量数组

  • 结构体深浅拷贝

    • 系统提供的赋值操作是 浅拷贝 – 简单值拷贝,逐字节拷贝
    • 如果结构体中有属性 创建在堆区,就会出现问题,在释放期间,一段内存重复释放,一段内存泄露
    • 解决方案:自己手动去做赋值操作,提供深拷贝

30. 结构体嵌套一级指针练习

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Person
{
	char *name;
	int age;
};

struct Person ** allocateSpace()
{
	struct Person ** tmp = malloc(sizeof(struct Person *) * 3);
	for (int i = 0; i < 3; i++)
	{
		tmp[i] = malloc(sizeof(struct Person));
		tmp[i]->name = malloc(1024);
		//strcpy(tmp[i]->name,"")
		sprintf(tmp[i]->name, "name[%d]", i + 100);
		tmp[i]->age = i + 100;
	}
	return tmp;
}

void printPerson(struct Person ** pArray, int len )
{
	for (int i = 0; i < len; i++)
	{
		printf("%s : %d", pArray[i]->name, pArray[i]->age);
		printf("\n");
	}
}

void freeSpace(struct Person ** pArray, int len)
{
	for (int i = 0; i < len; i++)
	{
		if (pArray[i]->name != NULL)
		{
			free(pArray[i]->name);
			pArray[i]->name = NULL;
		}
	}

	for (int i = 0; i < len; i++)
	{
		if (pArray[i] != NULL)
		{
			free(pArray[i]);
			pArray[i] = NULL;
		}
	}

		if (pArray != NULL)
		{
			free(pArray);
			pArray = NULL;
		}

}

void test01()
{
	struct Person ** pArray = NULL;

	pArray = allocateSpace();


	//打印数组
	printPerson(pArray, 3);

	//释放内存
	freeSpace(pArray,3);
	printPerson(pArray, 3);
	pArray = NULL;
}

int main()
{
	test01();
	system("pause");
	return EXIT_SUCCESS;
	
}

31. 内存对齐

查看对齐模数 #pragma pack(*show*)

默认对齐模数 8

  • 自定义数据类型 对齐规则

    第一个属性开始 从0开始偏移

    第二个属性开始 要放在 该类型的大小 与 对齐模数比 取小的值 的整数倍

    所有属性都计算完后,再整体做二次偏移,将整体计算的结果 要放在 结构体最大类型 与对齐模数比 取小的值的 整数倍上

  • 结构体嵌套结构体

    结构体嵌套结构体时候,子结构体放在该结构体中最大类型 和对齐模数比 的整数倍上即可

32. 文件读写回顾

  • 按照字符读写

    写 fputc

    读 fgetc

    while ( (ch = *fgetc*(f_read)) != *EOF* ) 判断是否到文件尾

  • 按行读写

    写 fputs

    读 fgets

  • 按块读写

    写 fwrite

    参数1 数据地址 参数2 块大小 参数3 块个数 参数4 文件指针

    读 fread

  • 格式化读写

    写 fprintf

    读 fscanf

  • 随机位置读写

    fseek( 文件指针, 偏移, 起始位置 )

    SEEK_SET 从头开始

    SEEK_END 从尾开始

    SEEK_CUR 从当前位置

    rewind 将文件光标置首

    error宏 利用perror打印错误提示信息

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

//按照字符读写文件:fgetc(), fputc()

void test01()
{
	//写文件

	FILE * f_write = fopen("./test01.txt", "w+");

	if (f_write == NULL)
	{
		return;
	}

	char buf[] = "this is first test";
	for (int i = 0; i < strlen(buf);i++)
	{
		fputc(buf[i], f_write);
	}

	fclose(f_write);

	//读文件
	FILE * f_read = fopen("./test01.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	char ch;
	while ( (ch = fgetc(f_read)) != EOF  )  // EOF  End of File
	{
		printf("%c", ch);
	}
	fclose(f_read);
}


//按照行读写文件:fputs(), fgets()
void test02()
{
	//写文件
	FILE * f_write = fopen("./test02.txt", "w");
	if (f_write == NULL)
	{
		return;
	}
	char * buf[] =
	{
		"锄禾日当午\n",
		"汗滴禾下土\n",
		"谁知盘中餐\n",
		"粒粒皆辛苦\n",
	};

	for (int i = 0; i < 4;i++)
	{
		fputs(buf[i], f_write);
	}

	fclose(f_write);

	//读文件
	FILE * f_read = fopen("./test02.txt", "r");
	if (f_read == NULL)
	{
		return;
	}
	while ( !feof(f_read ))
	{
		char buf[1024] = { 0 };
		fgets(buf, 1024, f_read);
		printf("%s", buf);
	}

	fclose(f_read);
}

//按照块读写文件:fread(), fwirte()
struct Hero
{
	char name[64];
	int age;
};
void test03()
{
	//写文件  wb二进制方式
	FILE * f_write = fopen("./test03.txt", "wb");
	if (f_write == NULL)
	{
		return;
	}

	struct Hero heros[4] = 
	{
		{ "亚瑟" , 18 },
		{ "赵云", 28 },
		{ "妲己", 19 },
		{ "孙悟空", 99 },
	};

	for (int i = 0; i < 4;i++)
	{
		//参数1 数据地址  参数2  块大小   参数3  块个数   参数4  文件指针
		fwrite(&heros[i], sizeof(struct Hero), 1, f_write);
	}

	fclose(f_write);

	//读文件  
	FILE * f_read = fopen("./test03.txt", "rb");  // read binary
	if (f_read == NULL)
	{
		return;
	}

	struct Hero temp[4];
	//参数1 数据地址  参数2  块大小   参数3  块个数   参数4  文件指针
	fread(&temp, sizeof(struct Hero), 4, f_read);

	for (int i = 0; i < 4;i++)
	{
		printf("姓名:%s  年龄:%d \n", temp[i].name, temp[i].age);
	}
	fclose(f_read);
}

//按照格式化读写文件:fprintf(), fscanf()
void test04()
{
	//写文件
	FILE * f_write = fopen("./test04.txt", "w");
	if (f_write == NULL)
	{
		return;
	}

	fprintf(f_write, "hello world %d年 %d月 %d日", 2018, 7, 5);

	//关闭文件
	fclose(f_write);


	//读文件
	FILE * f_read = fopen("./test04.txt", "r");
	if (f_read == NULL)
	{
		return;
	}

	char buf[1024] = { 0 };
	while (!feof(f_read))
	{
		fscanf(f_read, "%s", buf);
		printf("%s\n", buf);
	}

	fclose(f_read);
}

//按照随机位置读写文件
void test05()
{
	FILE * f_write = fopen("./test05.txt", "wb");
	if (f_write == NULL)
	{
		return;
	}

	struct Hero heros[4] =
	{
		{ "亚瑟", 18 },
		{ "赵云", 28 },
		{ "妲己", 19 },
		{ "孙悟空", 99 },
	};

	for (int i = 0; i < 4; i++)
	{
		//参数1 数据地址  参数2  块大小   参数3  块个数   参数4  文件指针
		fwrite(&heros[i], sizeof(struct Hero), 1, f_write);
	}

	fclose(f_write);


	//读取妲己数据
	FILE * f_read = fopen("./test05.txt", "rb");
	if (f_read == NULL)
	{
		//error 宏 
		
		//printf("文件打开失败\n");
		perror("文件打开失败");
		return;
	}

	//创建临时结构体
	struct Hero temp;

	//改变文件光标位置
	fseek(f_read, sizeof(struct Hero) *2, SEEK_SET);

	fseek(f_read, -(long)sizeof(struct Hero) * 2, SEEK_END);

	rewind(f_read); //将文件光标置首

	fread(&temp, sizeof(struct Hero), 1, f_read);

	printf("姓名: %s 年龄: %d\n", temp.name, temp.age);

	fclose(f_read);

}


int main(){
	//test01();
	//test02();
	//test03();
	//test04();
	test05();

	system("pause");
	return EXIT_SUCCESS;
}

33. 函数指针

  • 函数指针

    函数名本质就是一个函数指针

    可以利用函数指针 调用函数

  • 函数指针定义方式

    1、先定义出函数类型,再通过类型定义函数指针

    typedef void(FUNC_TYPE)(int, char);

    2、定义出函数指针类型,通过类型定义函数指针变量

    typedef void( * FUNC_TYPE2)(int, char);

​ 3、直接定义函数指针变量

void(*pFunc3)(int, char) = func;

  • 函数指针和指针函数

​ 函数指针 指向了函数的指针

​ 指针函数 函数返回值是指针的函数

  • 函数指针数组

    void(*pArray[3])();

  • 函数指针做函数参数(回调函数)

    利用回调函数实现打印任意类型数据

    提供能够打印任意类型数组函数

    利用回调函数 提供查找功能

34.链表反转练习

35.预处理指令

  • 头文件 #include

    <> “”区别

    <> 包含系统头

    “” 包含自定义头

    • 宏常量 大写 可以是常数

      不重视作用域

      没有数据类型

      不做语法检查

      有效范围在文件内

      利用 #undef 卸载宏

    • 宏函数

      将频繁、短小函数写成宏函数

      优点:以空间换时间

  • 条件编译

    #ifdef #else #endif 测试存在

    #ifndef #else #endif 测试不存在

    #if #else #endif 自定义条件编译

  • 特殊宏

    __FILE__ 宏所在文件路径

    __LINE__ 宏所在行

    __DATE__ 宏编译日期

    __TIME__ 宏编译时间

36.生成库配置

  • 静态库配置

    右键项目->属性 ->常规->配置类型 ->静态库

    生成项目 生成.lib文件

    将.lib和 .h交给用户

    测试

    静态库链接在编译期完成,在链接阶段复制到程序中,程序运行时与函数库再无瓜葛,移植方便,浪费空间

  • 动态库配置

    右键项目->属性 ->常规->配置类型 ->动态库 .dll

    生成项目 生成 .lib .dll

    静态库中生成的.lib和动态库生成的.lib是不同的,动态库中的.lib只会放变量的声明和 导出函数的声明,函数实现体放在.dll中

    库中声明 导出函数/外部函数 : __declspec(dllexport)int mySub(int a, int b);

    测试

    ​ 在测试中使用#pragma comment( lib,“./mydll.lib”)

    ​ 或者添加已有文件.dll .lib .h

37.递归函数

本质:函数自身调用自身

注意事项:递归函数必须有结束条件,函数有出口

void reversePrint(char * p)
{
	if (*p == '\0')
	{
		return;
	}

	reversePrint(p + 1);

	printf("%c\n", *p);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值