数据结构教程
前提:main中无功能,功能都写在main外函数中
为什么要用malloc?直接声明结点不好吗?
答:因为添加链表结点一般是在main函数以外的函数中,我们在函数里面定义的变量会放在本函数栈内,随着函数调用的结束,栈会清空,结点也没了
为什么函数传参用LinkNode *&L?
因为要改变main中定义的L存的指针值,所以在函数中直接引用main中的L。如果不改变L则不需要&
如初始化链表头结点时,在main中定义了一个LinkNode,需要在初始化函数中malloc,则要引用main中的指针变量
void InitList(LinkNode *&L){
L=(LinkNode *)malloc(sizeof(LinkNode));
L->next=NULL;
}
int main() {
LinkNode *l1;
InitList(l1);
return 0;
}
bool
为C++特有,形式是true、false,实质是0、1;
头文件
1、#include <stdio.h>声明了函数库,因此可以使用类如printf的函数
2、#include " "用于引用用户库,#include <>用于引用系统库
3、stdlib.h是通用工具函数库,stdio.h是标准的输入输出工具函数库。
main .h .c 函数引用
//main.c 函数使用
#include "test.h"
int main() {
fun1();
return 0;
}
//test.c 函数定义
#include <stdio.h>
#include "test.h"
void fun1() {
printf("ok");
}
//test.h 函数声明
void fun1();
变量
1、c语言区分大小写,即变量名大写和小写是不同的,且只能是字母、数字、下划线,第一个字符只能是字母或下划线
2、int 占四个字节
3、变量可以连续定义,但不能连续定义声明
int a=1;
//int b,c=a;//不能这样连续定义声明,这样相当于只初始了c
int b=a,c=a;//可以这样连续定义声明
变量名的本质是什么? 是变量地址的符号化,变量是为了让我们编程时更加方便,对人友好,可计算机可不认识什么变量 a,它只知道地址和指令。所以当你去查看 C 语言编译后的汇编代码,就会发现变量名消失了,取而代之的是一串串抽象的地址。你可以认为,编译器会自动维护一个映射,将我们程序中的变量名转换为变量所对应的地址,然后再对这个地址去进行读写。也就是有这样一个映射表存在,将变量名自动转化为地址。
printf与scanf
%-5.2f会至少显示5位数字并带有2位小数的浮点数,左对齐,在printf中使用
scanf("%d",&a);
//&是取地址运算符,scanf读取时,你得告诉他往哪里存储读进来的数据,
//所以要告诉他“地址”。
%s%d%f会忽略空格,%c不会,所以如果输入char前有空格,%c前必须留有空格
scanf("%d%s %c");//例如输入"1 小明 男"
两个scanf,第二个scanf是%c时,第一个scanf会造成缓冲区残留\n,下一个scanf会自动接收上一个scanf遗留的\n,所以不会暂停,解决办法是在两个scanf之间加上fflush(stdin)
当scanf是%d、%f时会自动清空缓冲区
debug调试时printf会暂存到输出缓冲区,可以用fflush(stdout)放在printf后面
不是所有编译器都支持fflush(stdin)、fflush(stdout),也可以用如下解决
char clear;
scanf("%c",&clear);//清除缓冲区回车符
scanf有返回值,是匹配成功的个数
运算符与表达式
关系表达式的值只有真和假,对应的值为 1和 0。由于 C 语言中没有布尔类型,所以在 C 语言中 0 值代表假,非 0 值即为真
逻辑非的优先级高于算术运算符,算术运算符的优先级高于关系运算符、关系运算符的优先级高于逻辑与和逻辑或运算符
数组
1、int arr[n] n必须是常量,不能是变量
只能在在定义数组时对数组元素赋初值。例如,
int a[10]={0,1,2,3,4,5,6,7,8,9};
不能定义后赋值
int a[10];a[10]={0,1,2,3,4,5,6,7,8,9}
2、数组仅声明不赋初值,会有随机值;只给前几个赋值,后几个自动赋0
3、访问定义范围外的数组会越界,会造成一起定义的变量改变
int arr[5];
arr[5];
4、字符数组有特殊的初始化方式,以字符串形式初始化,数组未满则自动补0,0转化为字符是\0,是结束符
char c[5]="abcd";
5、字符数组不必循环,可以直接printf,前提结尾有结束符\0,所以字符数组不要满
char c[5]="abc";
printf("%s",c);
6、scanf字符数组时不需要&变量名,因为数组变量名本身是指针
char c[10];
scanf("%s",c);
printf("%s",c);
//缺点是一行不能有空格,否则只输出空格前面的
用gets()代替scanf,可以完整获取一行,包括空格;puts()代替printf()
char c[10];
gets(c);//优于scanf("%s",c),一行可以用空格
puts(c);//代替printf("%s\n",c); 只能用于字符数组
//c++中用fgets(prioner,size,streamType(例stdin))
7、针对字符串的一些函数,引用#include “string.h”
strlen函数用于统计字符串长度,
strcpy函数用于将某个字符串复制到字符数组中
strcmp(arr1,arr2)函数用于比较两个字符串的大小(第一个字符的大小,如果相同依次向后),注意strcmp标准C中并不是返回-1和1,而是负值和正值
strcat函数用于将两个字符串连接到一起
8、数组a[10]在函数传递后,clion调试器debug只显示数组a[10]第一个元素
解决:点击+新建监视输入*(int(*)[10])(a)
9、将一个数组的元素复制到另一个数组中,用头文件#include <string.h>的memcpy(destin_array,source_array,byte_num)函数,byte_num是拷贝几个字节
指针
1、存放地址不能用普通的变量类型,必须用指针变量,如
int *a;
使用取地址符&可以取得变量地址
int i;
int *i_pointer=&i;//指针变量不要存放普通常量
2、取值符*,可以取指针所在的值(解引用)
int i=10;
int *a_p=&i;
printf("%d",*a_p);
引用
int i=10;
int &j=i;
printf("%d",j);
引用可传递实参,引用被创建的同时必须被初始化
C++ 中指针和引用的区别
3、指针可以通过+n、-n偏移地址,此时±的自动n*类型内存大小,如int的偏移即n×4
4、数组名传递给子函数时,传的是数组第一个元素的地址
5、使用malloc要引用#include “stdlib.h”;
int arr[10]是静态申请的内存,在栈中,用malloc(i)动态申请内存,在堆中malloc()返回的是无类型指针,所以需要强制类型转换,如(char *)malloc(size);
栈中的空间自动管理,一个函数结束会自动释放空间;而malloc申请的堆空间需要手动free()掉
6、指针大小是固定的,32位系统为四个字节
7、空指针是NULL,可用于指针判断
8、calloc与malloc用法类似,malloc申请后空间的值是随机的,并没有进行初始化,而calloc却在申请后,对空间逐一进行初始化,并设置值为0,一般用于自动初始化结构体免去麻烦;
(int*)calloc(1,sizeof(int));
//calloc的第一个参数是元素的个数,第二个参数是每个元素的长度
9、int a[n]与(int *)malloc(sizeof (int)*n)都是声明数组,前者是在栈中,后者是在堆中,堆的容量较大,相较于栈不容易导致溢出问题
参考int a[n]与int* a=(int*)malloc(sizeof(int)*n)的区别与联系
函数
1、默认情况下,只有后面定义的函数才可以调用前面定义过的函数;
void fun1(){
printf("fun1");
}
int main() {
fun1();
return 0;
}
如果想把函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数的前面进行函数的声明
void fun1();
int main() {
fun1();
return 0;
}
void fun1(){
printf("fun1");
}
2、局部变量可以和全局变量重名,重名时,改变的是局部变量的值
3、void函数里可以return
void fun(){
return;
}
结构体
1、结构体对齐 结构体内存大小不是所有属性加起来,而是最大属性大小的整数倍
2、访问结构体成员,非指针可以用结构体变量名+点
p.name
结构体指针访问成员,有两种方式:
(*p).name//不是*p.name,因为.优先级比*高
p->name//考试用这种
2、结构体不同于基本类型的使用
int i;//基本类型可直接声明
结构体类型在定义之前,要声明才能定义
struct student{
int id;
char name[10];
};
int main() {
struct student s1={1,"jojo"};
return 0;
}
很麻烦,所以在声明时可以用typedef起别名
typedef struct student{//主名student可以省略
int id;
char name[10];
}stu;
int main() {
stu s1={1,"jojo"};
return 0;
}
指针别名也可以同时设置
typedef struct student{
int id;
char name[10];
}stu,* stuList;
用结构体别名定义指针变量有两种方式
stu *s1;
stuList s2;
3、
结构体中的成员类型不能是该结构体本身,却可以是以该结构体类型为基本类型的指针类型
typedef struct BiTNode{//要设置结构体名
int data;
struct BiTNode *lchild,*rchild;//此处只认结构体名,不认别名
}BiTNode,*BiTTree;
结构体所占用内存必须在编译时确定,如果一个结构体中含有它本身,那么这个结构体所占用的内存大小是多少呢?这会循环形成一个永远也无法计算清楚的数值;如果是结构体指针,那么久好说了,指针只占用4个字节,因此可以成为结构体成员。
C++引用
int a=1;
int &b=a;
可以理解为,b就是a,并非创了新的b的内存装a的值,b是个快捷方式,引用这个词很到位,引用就是一种给已经存在的变量添加一个新的别名
指针引用int *&a 、二级指针int **
void change(int *c,int *d){
int *temp=c;
c=d;
d=temp;
}//指针变量c和d的值交换了
int main() {
int a=1;
int b=2;
int *pa=&a;//pa的值是a的地址
int *pb=&b;//pb的值是b的地址
change(pa,pb);
return 0;//pa和pb的值未变,即pa值还是a的指针,pb值还是b的指针
}
void change(int *&c,int *&d){//此时c和d是pa和pb的引用,完全可以看成pa和pb
在c中通过函数改变传入参数需要使用指针
void change(int *j){
*j=2;
}
int main() {
int i=1;
change(&i);
printf("%d",i);
return 0;
}
在c++中使用引用&,无需变量加*
void change(int &j){//省了*,多了&
j=2;
}
int main() {
int i=1;
change(i);//省了&
printf("%d",i);
return 0;
}
引用&必须在变量之前,中间不能有其他符号,需要改变指针时,&也要紧挨变量名
void function(char *&p){
p=(char *)malloc(100);
fgets(p,100,stdin);
}
引用必须赋值,避免了空值判断
C++布尔
c++比c多了布尔类型
bool b1= true;
bool b2= false;
顺序表
typedef int ElemType;//方便以后用其他数据类型,只需改int
链表
1、尾插法要加尾指针,为了防止每次插入新元素要从头遍历
2、结构体成员可以是指向自己结构体的指针,但是关键字不能是别名,而是结构体名,参考结构体包含指向自己的指针
typedef struct LNode{
ElemType data;
struct LNode *next;//此处struct关键字不能是别名Node
}Node, *LinkList;
队列
考顺序存储
顺序循环队列
为辨别空队列与满队列,一直要空一个元素的位置给rear。rear是要插入的位置,一直为空,所以数组队列满的时候rear在front前一个位置,里面为空
队空:rear=front
队满:(rear+1)%maxsize=front
二叉树
二叉树不会考顺序存储,考链式存储
位运算符
位运算符都是将整形视为二进制然后进行运算
1、移位 <<1是左移一位,>>1是右移一位
2、异或 i^j,例如i是5二进制是101,2二进制是010
101
010
____
111 =7
按位异或
0和任何数异或=数本身;一个数异或自身=0
异或有结合律: ( A ^ B ) ^ C = A ^ ( B ^ C )
浮点数
float f=4.5;
单精度,32位,第一位是符号位,表示该浮点类型的正负,0为正,1为负,4.5为正数,所以符号位为0;
尾数位为倒数23位,将4.5转成二进制为100.1,使其表现为1.xxx,所以为1.001*2^2,个位默认为1,只要小数部分,位数占不满在后面补零,所以尾数位为00100000000000000000000
指数位,由尾数推得,为(2的阶数+127)的二进制,2+127=129=10000001