typedef 与宏定义区别**
语法:
typedef的语法是 typedef 已有的数据类型 新类型名称;,例如 typedef int Integer;。
宏定义的语法是 #define 标识符 替换文本,例如 #define PI 3.14159。
作用范围
typedef创建的类型别名的作用范围是局部的,只在当前的作用域中有效。
宏定义是全局有效的,可以在多个文件中使用。
类型检查:
typedef在编译时进行类型检查,使用typedef定义的别名会受到编译器的类型检查。这意味着在使用typedef定义的类型别名时,编译器会确保使用的是正确的类型。
宏定义只是简单的文本替换,没有类型的概念,不进行类型检查。
因此,使用宏定义可能存在类型不匹配的问题。
可读性:
typedef可以提高代码的可读性,通过给类型取一个有意义的别名,使得代码更易于理解。
宏定义的替换文本可能不容易理解,可读性较差。
需要注意的是,C语言中的宏定义具有更大的灵活性,可以用来定义常量、函数宏、条件编译等,而typedef主要用来创建类型别名。
C进程内存布局
栈内存有什么特点?
-
空间有限,尤其在嵌入式环境下,尤其不可以用来存储尺寸太大的变量,在Linux栈内存大小为8M
-
每当一个函数被调用,栈就会向下增长一段,用以存储该函数的局部变量
-
每当一个函数退出,栈就会向上缩减一段,将该函数的局部变量所占内存归还给系统。
-
栈空间申请的变量随着函数结束,空间自动释放
数据段与代码段
-
数据段细分成如下几个区域:
-
.bss 段:存放未初始化的静态数据,它们将被系统自动初始化为0
-
.data段:存放已初始化的静态数据
-
.rodata段:存放常量数据
-
-
代码段细分成如下几个区域:
-
.text段:存放用户代码
-
.init段:存放系统初始化代码
-
局部变量与栈内存
-
局部变量概念:凡是被一对花括号包含的变量,称为局部变量
-
局部变量特点:
-
某一函数内部的局部变量,存储在该函数特定的栈内存中
-
局部变量只能在该函数内可见,在该函数外部不可见
-
当该函数退出后,局部变量所占用的内存立即被系统回收,因此局部变量也称为临时变量
-
函数的形参虽然不被花括号包含,但依然属于该函数的局部变量
-
-
栈内存特点:
-
每当一个函数被调用时,系统将自动分配一段栈内存给该函数,用于存放其局部变量
-
每当一个函数有退出时,系统将自动回收其栈内存
-
系统为函数分配栈内存时,遵循从上(高地址)往下(低地址)分配原则
-
int max(int x, int y)// 变量x和y存储在max()函数的栈中
{
int z;//变量z存储在max()函数的栈中
z = x > y ? x : y;
return z; // 函数退出后,栈中的x、y和z被系统回收
}
int main(void)
{
int a = 1;// 变量a存储在main()函数的栈中
int b = 2;// 变量b存储在main()函数的栈中
int m;// 变量m存储在main()函数的栈中,未赋值所以其值为随机值
m = max(a,b);
}
静态数据(面试必考)
c语言中,静态数据有两种:
-
全局变量:定义在函数外的变量
-
静态局部变量:定义在函数内部,且被static修饰的变量
为什么需要静态数据
-
全局变量在默认的情况下,对所有文件可见,为某些需要在各个不同文件和函数之间访问的数据提供操作上的方便。
-
static修饰的全局变量,只能在本文件使用,如果未被static修饰的全局变量,所有的文件都能使用,会出现命名污染。
-
当我们希望一个函数退出后依然能保留局部变量的值,以便于下一次调用时还能用,静态局部变量可以帮助实现这样的功能。
注意
-
-
若定义时未初始化,则系统会将所有的静态数据自动初始化为0
-
静态数据初始化语句,只会执行一遍。
-
静态数据从程序开始运行时便已存在,直到程序退出时才释放。
-
-
注意2:
-
static修饰局部变量:使之由栈内存临时数据,变为静态数据
-
static修饰全局变量:使之由各文件可见的静态数据,变成为本文件可见的静态数据
-
static修饰的函数:使之由各文件可见的函数,变成为本文件可见的静态函数。
-
从键盘输入一个字符串,然后按照下面要求输出一个新字符串。
新串是在原串中,每两个字符之间插入一-个空格,如原串为abcd,
则新串为ab cd。要求在函数insert中完成新串的产生﹔
(笔试重点)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void insert_space(char *newStr,char *str)
{
int cnt = 0;
for(int i = 0; i < strlen(str); i++)
{
newStr[cnt++] = str[i];
if((i+1) % 2 == 0)
{
newStr[cnt++] = ' ';
}
}
}
int main(int argc, char const *argv[])
{
char str[] = "aabbccddee";
char newStr[strlen(str) * 2];
// 清空数组
memset(newStr, 0, strlen(str)*2);
//bzero(newStr,strlen(str)*2);
insert_space(newStr,str);
printf("%s\n",newStr);
return 0;
}
堆内存基本特征:
-
相比栈内存,堆的总大小仅受限于物理内存,在物理内存允许的范围内,系统对堆内存的申请不做限制。
-
相比栈内存,堆内存从下往上增长。
-
堆内存是匿名的,只能由指针来访问。
-
自定义分配的堆内存,除非开发者主动释放,否则永不释放,直到程序退出。
练习:用对空间实现冒泡排序
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void sort(int *p,int len)
{
int temp;
for(int i=0;i<len-1;i++)
{
for(int j=0;j<len-i-1;j++)
{
if(p[j]>p[j+1])
{
temp=p[j];
p[j]=p[j+1];
p[j+1]=temp;
}
}
}
}int main(int argc, char const *argv[])
{
int *p = malloc(sizeof(int)*10);//申请堆空间
//bzero(p,sizeof(int)*10);
//int a[8]={2,3,5,7,1,0,4,3};
//memcpy(p,a,sizeof(int)*8);
//也是个方法,利用memcpy把数据拷贝过去
for(int i=0;i<10;i++)
{
scanf("%d",&p[i]);
}
getchar();//拿掉回车键sort(p,10);
for(int i=0;i<10;i++)
{
printf("%d",p[i]);}
//释放空间
free(p);
p=NULL;//空间释放了,指针与原空间断开联系,此时要把指针指向空,不然野指针
return 0;
}
用malloc开辟二维数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(int **ptr, int row, int col)
{
for(int i = 0; i < row; i++)//输出
{
for(int j = 0; j < col; j++)
{
printf("%d\t",ptr[i][j]);
}
printf("\n");
}
}
int main(int argc, char const *argv[])
{
// 数组[]表示数据的个数,总大小是数据个数*类型
int buf[3];// 3*4
// malloc实参是以字节为单位,相当于一维数组
int *ptr = malloc(3*sizeof(int));
for(int i = 0; i < 3; i++)
{
ptr[i] = i;
}
// 将mallc转为二维数组
// 3行
int **ptr1 = (int **)malloc(3*sizeof(int));
for(int i = 0; i < 3; i++)
{
for(int j = 0; j < 4; j++)
{
ptr1[i] = (int *)malloc(4*sizeof(int));
}
}
int count = 0;
int i,j;
for(i = 0; i < 3; i++)//输入
{
for(j = 0; j < 4; j++)
{
ptr1[i][j] = ++count;
}
}
func(ptr1,i,j);
free(ptr1);
ptr1 = NULL;
return 0;
}
以上是法一,法二如下
#include <stdio.h>
#include <stdlib.h>
#include <string.h>void func(int (*ptr)[4], int row, int col)
{
for(int i = 0; i < row; i++)
{
for(int j = 0; j < col; j++)
{
printf("%d\t",ptr[i][j]);
}
printf("\n");
}
}int main(int argc, char const *argv[])
{
// 定义数组指针,每个指针指向4个元素的首地址
int (*p)[4] = (int (*)[4])malloc(3*4*sizeof(int));
int count = 0;
int i,j;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
p[i][j] = ++count;
}
}func(p,i,j);
// for(int i = 0; i < 3; i++)
// {
// for(int j = 0; j < 4; j++)
// {
// printf("%d\t",p[i][j]);
// }
// printf("\n");
// }return 0;
}
释放内存的含义:
-
释放内存意味着将内存的使用权归还给系统。
-
释放内存并不会改变指针的指向。
-
释放内存并不会对内存做任何修改,更不会将内存清零。
函数指针:
//demo:
#include <stdio.h>typedef int int32_t;
// 给函数指针该别名,方便使用,增加指针的易用性
// 此时fptr就相当于void (*fptr)(int *a, int *b)的类型
typedef void (*fptr)(int *a, int *b);// 类似于 int类型 int a = 10
typedef int (*fMaxPtr)(int,int);// 函数指针的参数可以只写变量类型void swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}int max(int a, int b)
{
return a > b ? a : b;
}int main(int argc, char const *argv[])
{
int a = 10 , b = 20;
// 定义函数指针指向swap
// 注意pfunc指针的类型与所指向的函数类型一致
// 将函数名去掉,剩下的部分为函数的类型
void (*pfunc)(int *a, int *b) = swap;
printf("%p\n",swap);
printf("%p\n",pfunc);// 2. 将函数指针改别名
fptr p = swap;
printf("%p\n",p);// 通过函数指针执行swap函数
//p(&a,&b);
pfunc(&a,&b);
printf("%d,%d\n",a,b);// 定义函数指针指向max函数,实现比较最大值
fMaxPtr fmax = max;
printf("max = %d\n",fmax(19,29));return 0;
}
函数指针数组
//dmeo:
#include <stdio.h>
#include <string.h>// 设计函数指针数组类型,增加易用性
typedef int (*fpbuf[2]) (int,int);
int Max(int a, int b)
{
return a > b ? a : b;
}int Min(int a, int b)
{
return a < b ? a : b;
}int main(int argc, char const *argv[])
{
// 定义函数指针数组
int (*pbuf[2]) (int,int) = {Max,Min};
// 输出最小值
printf("min = %d\n",pbuf[1](10,20));fpbuf fbuf = {Max,Min};
// 输出最小值
printf("Max = %d\n",fbuf[0](10,20));return 0;
}
冒泡函数设计的回调(升序和降序)
#include "bubble.h"
#include "asc.h"
int main(int argc, char const *argv[])
{
int array[] = {3,1,2,4,5};
// 升序
//bubble(array,5,ascend);
// 降序
bubble(array,5,descend);
for(int i = 0; i < 5; i++)
{
printf("%d\t",array[i]);
}
return 0;
}
#include "asc.h"
// 升序
bool ascend(int a, int b)
{
return (a-b > 0) ? true : false; // > 0
}
// 降序
bool descend(int a, int b)
{
return (a-b < 0) ? true : false; // < 0
}
#ifndef _ASC_H
#define _ASC_H
#include <stdbool.h>
// 升序
extern bool ascend(int a, int b);
// 降序
extern bool descend(int a, int b);
#endif
内联的特性 :
以空间换时间
-
当编译器发现某段代码有inline关键字的时候就会将这段代码插入到当前的位置,加快运行效率,但是也会消耗一定的运行空间
-
什么时候用inline
-
函数需要频繁被调用,代码最好不要超过5行
-
-
inline注意事项
-
内联函数在头文件实现,其它函数不要在头文件实现
-
函数声明和函数实现都需要添加关键字inline,如果函数声明没有添加extern 和 inline 关键字,会报错
-
// 创建add.h头文件
#ifndef _ADD_H
#define _ADD_H
#include <stdio.h>
// 内联函数声明
extern inline int add(int a, int b);
#endif
// 创建main.c
#include "add.h"
inline int add(int a, int b)
{
return a+b;
}
int main(int argc, char const *argv[])
{
printf("%d\n",add(10,20));
return 0;
}
递归函数(自己调用自己)
-
递归概念:如果一个函数内部,包含了对自身的调用,则该函数称为递归函数。
#include <stdio.h>
void func(int n)
{
// 递归退出条件
if(n == 6)
return;func(++n);
printf("%d\n",--n);
}int main(int argc, char const *argv[])
{
int n = 1;
func(n);
return 0;
}
#include <stdio.h>
int func(int n)
{
// 退出条件
if(n == 1)
return 1;return func(n-1)*n;
}int main(int argc, char const *argv[])
{
int n = func(4);
printf("%d\n",n);
return 0;
}(阶乘)