数据结构中C语言的知识
数据结构中C语言的知识
在数据结构的学习中会遇到一些C语言中的一些知识,这篇文章就对此做一个大概的复习
一、函数
对于从1一直加到100,在C语言中我们可以如下的方式
#include int main(){int a = 1,sum = 0;for (int i = 1;i <= 100;i++){sum = a + sum;a++;}printf(\"%d\\n\", sum);return 0;}
但在实际应用中,这种简单的代码在一个程序中往往会使用多遍,所以函数就有了用武之地。
#include void add(){int a = 1, sum = 0;for (int i = 1;i <= 100;i++){sum = a + sum;a++;}printf(\"%d\\n\", sum);}int main(){add();}
由此可见函数就是一段代码的封装。
函数的作用
1.实现某个具体功能
2.增加复用性
3.降低难度
函数的特点
1.不调用不执行
2.对内隐藏细节,对外暴露接口
而对于函数的需求我个人简易地分了四种情况
1.无参数无返回值
2.无参数有返回值
3.有参数无返回值
4.有参数有返回值
总而言之一句话——根据需求来写函数
二、字符串
在C语言中没有字符串这一数据类型,通常用数据来进行初始化
int main(){char str[] = \"HelloWorld\";}
注意这里有俩点需要注意
1.该字符串虽然由10个字母组成,但在内存中却占据11个char类型的空间——字符串的末尾有\"\\0\"。
2.数组名是常量无法单独赋值,下面一段代码就一个常见的错误。
int main(){char str[11];str = “HelloWorld”;}
在C语言的函数库中有一个函数可以直接给字符串赋值——strcpy()
#include #include int main(){char str1[11];strcpy(str1, \"HelloWorld\");}
注意:使用strcpy函数时要加string头文件
三、数组
数组的几个性质
1.相同数据类型的集合
2.数组长度一旦定义就不能改变
3.在数组中用下标来表示每一个数组元素的位置,数组下标从0开始
数组地址是数组下标0的地址即首元素的地址
下面用一段代码来验证
int main(){int a[] = { 1,2,3,4,5 };printf(\"%p\\n\", &a);printf(\"%p\\n\", &a[0]);}
通常情况下,数组名会转换为指向数组首元素的地址(但在sizeof(a)中的a表示整个数组)
从结果可以证明数组地址是数组下标0的地址即首元素的地址。
其中%p是输出地址的格式符
在这里介绍一个函数来求数组在内存中所占的字节数——sizeof()
int main(){int a[] = { 1,2,3,4,5 };printf(\"%zu\\n\", sizeof(a));printf(\"%zu\\n\", sizeof(a[0]));}
面板中第一行值表示的是数组在内存中占的字节数,第二行则是数组中首元素在内存中占的字节数
根据上述的文字,我们可以设计一段代码来计算未知数组的长度
int main(){int a[] = { 1,2,3,4,5 };int len = sizeof(a) / sizeof(a[0]);printf(\"%d\\n\", len);}
其中len就表示数组的长度,%zu是sizeof的格式符。
所以当我们在遍历数组时就可以使用这段代码
int main(){int a[] = { 1,2,3,4,5 };int len = sizeof(a) / sizeof(a[0]);for (int i = 0;i < len;i++){printf(\"%d\\n\", a[i]);}}
四、指针
指针简单来说就是存放内存地址的变量,因此指针==地址
指针的声明
int main(){int a;int* p;}
其中第一行是对整型变量的声明,第二行就是对指针的一个声明,该指针指向一个整型的地址。
对指针声明时的*的位置是随意的
指针的本质
用一段代码来解释
int main(){int a = 5;int* p;p = &a;printf(\"a的地址:%p,a的值:%d\\n\", &a, a);printf(\"p的地址:%p,p的值:%p\\n\", &p, p);}
在图中指针p的值与a的地址相同,所以指针的本质就是地址。
指针的使用
在这里介绍一个操作符——“*”
这里的*号与上文中的不一样,上面的是对指针的声明,而这个是间接引用操作符,也就是解(引用)指针,它返回的是指针变量指向地址的值。
int main(){int a = 5;int* p;p = &a;printf(\"%d\\n\", *p);*p = 10;printf(\"%d\\n\", a);}
在对指针声明后,*p就表示p所代表的地址中的元素
在这里可以简单理解成p=&a; *p=*&a=a
其中二者相互抵消
因为指针就是地址,所以一般情况下指针会与函数,数组,结构体等一起使用
指针与函数
现在要求设计一个函数,传入两个整型变量并交换它们的值
在不用指针时的代码如下
void swap(int a, int b){int temp;temp = a;a = b;b = temp;printf(\"%d %d\\n\", a, b);}int main(){swap(5, 10);}
其中输出结果为10,5
但是这段代码中a和b在内存中的值却没有发生变化,用一段代码来验证
void swap(int a, int b){int temp;temp = a;a = b;b = temp;printf(\"%d %d\\n\", a, b);}int main(){int m = 5;int n = 10;swap(m, n);printf(\"m=%d,n=%d\\n\", m, n);}
这是因为在swap函数中的变量是局部变量,对局部变量进行值传递不影响主函数中变量的值
但如果使用指针就可以实现主函数中变量值的交换
void swap(int* a, int* b){int temp;temp = *a;*a = *b;*b = temp;printf(\"a=%d,b=%d\\n\", *a, *b);}int main(){int m = 5;int n = 10;swap(&m, &n);printf(\"m=%d,n=%d\\n\", m,
因为地址是唯一的,所以对指针(地址)的改变会作用到主函数中
指针与数组
通过数组下标能完成的操作都可以通过指针来完成
一般来说,用指针比用数组下标执行更快
int main(){int a[] = { 1,2,3,4,5 };int* p;p = a;printf(\"%p\\n\", a);printf(\"%p\\n\", p);printf(\"%d\\n\", *p);}
由此可见指针p中存放着数组首地址
因此,对数组的遍历还可以用指针来实现
int main(){int a[] = { 1,2,3,4,5 };int* p;p = a;int len = sizeof(a) / sizeof(a[0]);for (int i = 0;i < len;i++){printf(\"%d\\n\",*(p + i));}}
代码中对指针的算术运算是指对步长的运算
以上述代码为例,p+i->p+4i
下图是各数据类型在32位和64位操作系统下所占的字节数
五、结构体
结构体是一个或多个变量的集合,这些变量可以是不同的类型
结构体的声明
struct 结构体名{数据类型 变量名;……………… …………;};
举一个例子:用结构体来表示一个在平面直角坐标系上的一个点。
struct point{int x;int y;};
其中结构体中存放的就是横纵坐标值
结构体的初始化和调用
初始化
struct 结构体名 变量名;
赋值
其中要引入一个操作符\".\"
变量名 . 结构体内部变量名 = 值;
还用上述例子来验证这两点
struct point{int x;int y;};int main(){struct point p;p.x = 5;p.y = 10;printf(\"%d %d\\n\", p.x, p.y);return 0;}
结构体与函数
这里用一个例子来说明
struct point{int x;int y;};struct point creatPoint(int x, int y){struct point temp;temp.x = x;temp.y = y;return temp;}int main(){struct point p;p = creatPoint(5, 10);printf(\"%d\\n\", p.x);printf(\"%d\\n\", p.y);}
其中creatPoint函数就利用结构体来实现了传入两个参数,创建了一个点
结构体与指针
在上述例子中将函数的返回值赋值给变量很不方便,这时用指针就可以更直接
#include #include struct point{int x;int y;};struct point* creatPoint(int x, int y){struct point* temp = (struct point*)malloc(sizeof(struct point));temp->x = x;temp->y = y;return temp;}int main(){struct point* p;p = creatPoint(5, 10);printf(\"%d\\n\", (*p).x);printf(\"%d\\n\", (*p).y);free(p);}
在上述代码中用了两种方式来表示结构体的值,其中->形式是专门对指针的引用
上述代码中还设计到了对动态内存的申请和释放,这个问题在下文中会解释
类型定义函数
使用
typedef 数据类型 别名;
例子
typedef int zx;int main(){zx a;a = 1;printf(\"%d\\n\", a);}
在上述代码中就相当于用zx来代替数据类型int
作用
在程序中往往一大段代码中会大量使用某一个数据类型,如果因为需求需要将这个数据类型变成另一种数据类型,使用typedef就可以在主函数外来实现这一要求。
typedef与结构体
每次声明和调用结构体都要写struct关键字,很麻烦且在逻辑上十分难受,使用typedef就可以解决这一点
typedef struct (结构体名){数据类型 变量名;数据类型 变量名;………………… ……………;}别名;
其中结构体名可写可不写
六、内存分类
C语言程序编译后,会以三种形式使用内存
1.静态/全局内存
静态变量和全局变量使用,程序开始运行市分配,直到程序终止消失。
2.栈内存/自动内存
函数内部声明变量使用这一部分内存,在函数被调用时才创建,每次写完代码,自动开辟空间,无法控制开辟和释放。
3堆内存/动态内存
根据需求编写代码,动态分配内存,可以写函数来进行释放,内存中的内容直到释放才消失。
使用malloc函数来进行动态内存分配
成功:返回从堆内存上分配内存的指针
失败:返回空指针
引用上文中的实例代码,可以更好地理解分配内存的步骤
#include #include struct point{int x;int y;};struct point* creatPoint(int x, int y){struct point* temp = (struct point*)malloc(sizeof(struct point));temp->x = x;temp->y = y;return temp;}int main(){struct point* p;p = creatPoint(5, 10);printf(\"%d\\n\", (*p).x);printf(\"%d\\n\", (*p).y);free(p);return 0;}
1、使用malloc函数时,要注意加上#include 头文件
2、注意使用完变量后,应及时释放
3、malloc函数的括号中时所引用的数据类型在内存中所占的字节