C语言学习记录

一、C语言关键字

由ANSI标准定义的C语言关键字共32个:

 *auto  double  int  struct  break  else  long  switch
 *case  enum  register  typedef  char  extern  return  union
 *const  float  short  unsigned  continue  for  signed  void
 *default  goto  sizeof  volatile  do  if  while  static

1.数类型关键字(12)

数据类型关键字共12个
char、short、int、long、signed、unsigned、float、double、struct、union、enum、void

(1)unsigned :无符号的
用来声明一个无符号的变量。
unsigned char var; //var的范围:0~255

(2)signed :有符号的(可以省略不写)
用来声明一个有符号的变量。
signed char var; //var的范围:-128~127

(3)char :字符型
用来声明一个字符型变量。
占一个字节空间
char var;

(4)int :整型
用来声明一个整型变量。
C51:占两个字节空间,ARM:占四个字节
int var;

(5)float :浮点型
用来声明一个浮点型(实型)变量。
最多能表示到7个有效数据位。
占四个字节空间
float var;

(6)double :双精度型
//用来声明一个双精度实型变量。
//最多能表示到15~16个有效数据位。
//占四个字节空间 ,有的系统占八个字节
double var;

(7)short :短整型
用来声明一个短整型变量。
C51:跟int一样,ARM:占两个字节
short var;

(8)long :长整型
用来声明一个长整型变量。
ARM:跟int一样,C51:占四个字节
long var;

(9)void :空型
表示一个函数没有返回值,或者无形参。
void function(void);

(10)struct
用来声明一种结构体类型。
struct stu{
char sex;
int age;
float score;
struct stu *Next;
};
struct stu var;

(11)union
用来声明一种共用体类型。
该类型的变量所在空间的大小以其成员占最大的那个为准,
存入该变量中的值以程序中最后存入的数值为当前值
union non{
char sex;
int age;
float score;
};
union non var;

(12)enum
用来声明一种枚举类型。
规定枚举类型的变量,只能在限定的范围内取值
否则,编译会出现警告(达到数据安全的效果)
enum em
{a = 23,b,c,d = 56,e}; //其中b=24,c=25,e=57
enum em var;

2.流程控制关键字(12)

1、循环控制(5个)
for、do、while、break、continue

C语言中有三种循环语句:while循环、do-while循环和for循环。continue和break的不同之处在于其是跳过本次循环进入下一次循环,而break则是直接跳出循环。

2、条件语句(3个)
if、else、goto

(1)if,else语句
要注意,else总是与离它最近的未被匹配的if相匹配。

if (表达式)
语句1;
else
语句2;

//多分支
if (表达式1)
语句1;
else if (表达式2)
语句2;
else
语句3;

//嵌套
if (表达式1) {
	语句1;
	if (表示式x) {
		语句x;
	}
	else {
		语句y;
	}
}
else if (表达式2) {
	语句2;
}
else {
	语句3;
}

(2)goto语句
goto语句可以实现跳转,跳转到标签所在位置,但是不能跨代码块实现:

int main()
{
	int i = 0;
START:
	printf("[%d]goto running ... \n", i);
	Sleep(1000);
	++i;
	if (i < 10) {
		goto START;
	}

	printf("goto end ... \n");
	system("pause");
	return 0;
}

3、开关语句(3个)
switch、case、default

首先switch括号内表达式应该是整形表达式或者一个整型值
case则是对应的整型表达式的值或者对应的整型值,根据表达式的值转到对应的case语句。
break是用来跳出switch语句。每个case语句后都加上break。
而default是为了处理case语句中没有的特殊情况。

int main()
{
	int day = 1;
	switch (day) {
	case 1:
		printf("星期一\n");
		break;
	case 2:
		printf("星期二\n");
		break;
	case 3:
		printf("星期三\n");
		break;
	case 4:
		printf("星期四\n");
		break;
	case 5:
		printf("星期五\n");
		break;
	case 6:
		printf("星期六\n");
		break;
	case 7:
		printf("星期日\n");
		break;
	default:
		printf("bug!\n");
		break;
	}
	system("pause");
	return 0;
}

4、返回语句(1个)
return

return用于终止一个函数,并返回其后面跟随的值。例如:

char* show()
{
	char str[] = "hello world";
	return str;
}
int main()
{
	char* s = show();
	printf("%s\n", s);
	system("pause");
	return 0;
}

3.存储类型关键字(5)

auto、extern、register、static、typedef

(1)auto关键字
在代码块内部定义的变量是局部变量,而局部变量默认是用auto修饰的,但是一般都会省略。

#include <stdlib.h>
#include <stdio.h>

int main()
{
	auto int j = 0;
	printf("%d\n",j);
	system("pause");
	return 0;
}

(2)extern关键字
extern变量称为外部存储变量,extern声明了程序中将要用到但尚未定义的外部变量。通常外部储存都用于声明在另一个转换单元中定义的变量。
一个工程是由多个c文件组成的。这些源代码文件会分别进行编译,然后链接成一个可执行的模块。把这样的一个程序作为一个工程进行管理,并且生成一个工程文件来记录所有包含源代码文件。如下图,main.c和time.c中的变量互相调用

在这里插入图片描述

在这里插入图片描述

(3)register关键字
register:这个关
键字请求编译器尽可能地将变量存在CPU内部寄存器中,而不是通过内存寻址访问以提高效率。注意是尽可能,不是绝对。可以想象,一个CPU的寄存器数量有限,也就那么几个或几十个,如果用户定义了很多很多register变量,会把CPU的寄存器占满。
同时register变量是不能被取地址的。因为取地址这种寻址操作只能在内存中进行。

#include <stdlib.h>
#include <stdio.h>
#include "Test.h"
int main()
{
	register int a = 27;
	printf("a = %p\n",a);
	system("pause");
	return 0;
}

在这里插入图片描述
同时,使用寄存器变量时应该注意以下几点:

1.应当使用局部变量,因为全局变量会导致寄存器被长时间占用,可能会对计算机的状态造成影响。

2.应当在需要高频使用的时候用register关键字修饰局部变量。

3.如果要使用,也应当注意不能过多使用,因为寄存器数量是有限的,同时大都用来存储一些重要的状态信息

(4)static关键字
static变量为静态变量,将函数的内部变量和外部变量声明成static的意义是不一样的。

static的两个重要作用:

1、修饰变量。变量又分为局部变量和全局变量,但它们都存在内存的静态区。
(1)修饰全局变量:
static修饰的全局变量只能够在本文件内部被使用。
(2)修饰局部变量:
对于局部变量来说,其生命周期是在定义的代码块内部。但是如果用static修饰局部变量。其生命周期就会变成全局变量的生命周期。但是要注意,虽然其生命周期发生改变,但是其作用域却不发生改变。

#include <stdlib.h>
#include <stdio.h>
#include <stdio.h>
void AddOne(){
	static int j = 0;  //定义static变量
	j = j + 1;
	printf("%d\n", j);
}
int main(){
	printf("第一次调用:");
	AddOne();
	printf("第二次调用:");
	AddOne();
	return 0;
}

在这里插入图片描述
可见static int j = 0;只在第一次调用时初始化。

2、修饰函数。函数前加static使得函数成为静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数是否会与其他文件中的函数同名。

(5)typedef关键字
typedef是用来对类型进行重命名的:
对一般类型进行重命名
对结构体类型进行重命名
对指针进行重命名
对复杂结构进行重命名

可参考第 八、typedef关键字

4.其他关键字( 3 个)

const、volatile、sizeof

(1)const关键字
const是constant的缩写,是恒定不变的意思,也翻译为常量和常数等。
const修饰的变量,不可直接被修改。但是却可以通过指针的解引用来进行修改。

const修饰指针变量分为几种情况:
const int *p; // p 可变,p 指向的对象不可变
int const *p; // p 可变,p 指向的对象不可变
int *const p; // p 不可变,p 指向的对象可变
const int *const p; //指针 p 和 p 指向的对象都不可变

同时const也可以修饰函数参数,const 修饰符也可以修饰函数的返回值,返回值不可被改变。例如:

const int Fun (void);

也可以在另一连接文件中引用 const 只读变量:
extern const int i; //正确的声明
extern const int j=10; //错误!只读变量的值不能改变

(2)volatile关键字
volatile关键字和const一样是一种类型修饰符,用它修饰的变量表示可以被某些编译器未知的因素更改,比如操作系统、硬件或者其他线程等。遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。

例:

int i=10;
int j = i;//(1)语句
int k = i;//(2)语句

这时候编译器对代码进行优化,因为在(1)、(2)两条语句中,i 没有被用作左值。这时候编译器认为 i 的值没有发生改变,所以在(1)语句时从内存中取出 i 的值赋给 j 之后,这个值并没有被丢掉,而是在(2)语句时继续用这个值给 k 赋值。这是一种内存被“覆盖”的情况。**编译器不会生成出汇编代码重新从内存里取 i 的值,这样提高了效率。**但要注意:(1)、(2)语句之间 i 没有被用作左值才行。

再看另一个例子:

volatile int i=10;
int j = i; //(3)语句
int k = i; //(4)语句

volatile 关键字告诉编译器 i 是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值,因而编译器生成的汇编代码会重新从 i 的地址处读取数据放在k中。这样看来,如果 i 是一个寄存器变量或者表示一个端口数据或者是多个线程的共享数据,就容易出错,所以说 volatile 可以保证对特殊地址的稳定访问。

所以,volatile可以忽略编译器优化,保持内存可见性。

(3)sizeof关键字
siezof的主要作用就是计算数据类型长度。

各个类型的字节数:

int main()
{
printf("char %d\n", sizeof(char)); //1
printf("short %d\n", sizeof(short)); //2
printf("int %d\n", sizeof(int)); //4
printf("long %d\n", sizeof(long)); //4
printf("long long %d\n", sizeof(long long)); //8
printf("float %d\n", sizeof(float)); //4
printf("double %d\n", sizeof(double)); //8
return 0;
}

在这里插入图片描述

二、函数学习

1.函数调用

我们需要用到自定义的函数的时候,就得调用它,那么在调用的时候就称之为函数调用。
在C语言中,函数调用的一般形式为:

函数名([参数]; 

[]中可以是常数,变量或其它构造类型数据及表达式,多个参数之间用逗号分隔。

2.有参与无参

在函数中不需要函数参数的称之为无参函数,在函数中需要函数参数的称之为有参函数。

有参和无参函数的一般形式如下:

在这里插入图片描述

3.形参与实参

函数的参数分为形参和实参两种。

  • 形参
    形参是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。

  • 形参特点:
    形参只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。
    形式参数有自己的内存空间,当函数被调用时才申请了该空间,才有了这个变量同时被赋值为实际参数的值。当函数执行结束后,该空间被内存管理单元自动回收。及释放空间

  • 实参
    实参是在调用时传递该函数的参数

  • 实参特点
    实参可以是常量、变量、表达式、函数等。
    无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值等办法使实参获得确定值。
    在参数传递时,实参和形参在数量上,类型上,顺序上应严格一致,否则会发生类型不匹配的错误。

4.函数的返回值

函数的返回值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。

函数的返回值要注意以下几点:

  • 函数的值只能通过return语句返回主调函数。
return 表达式   或者为:  return (表达式);
  • 函数值的类型和函数定义中函数的类型应保持一致。
    ps: 如果两者不一致,则以函数返回类型为准,自动进行类型转换。
  • 没有返回值的函数,返回类型为 void。

5.递归函数

递归就是一个函数在它的函数体内调用它自身。

执行递归函数将反复调用其自身,每调用一次就进入新的一层。
注意递归函数必须有结束条件
阶乘计算的应用:

#include <stdio.h>
#include <stdlib.h>

int digui(int num){

	int result;

	//越界判断!
	if(num >= 17){
		printf("越界,超出计算机计算位数\n");
		exit(-1);
	}
	if(num == 0){

		return 1;
	}else{

		return num*digui(num-1);
	}

}

int main(int argc, char const *argv[])
{
	int num;
	int result;
	printf("需要计算的阶乘:\n");
	scanf("%d",&num);
	result = digui(num);
	printf("%d 的阶乘为 %d\n",num,result);	
	return 0;
}

递归图解:
在这里插入图片描述

三、数组学习

1.数组介绍

程序中也需要容器,只不过该容器有点特殊,它在程序中是一块连续的,大小固定并且里面的数据类型一致的内存空间,它还有个好听的名字叫数组

  • 数组声明:
数据类型 数组名称[长度];
int arr[10];
  • 数组初始化
数据类型 数组名称[长度n] = {元素1,元素2…元素n};
int arr[3] = {0,1,2};
数据类型 数组名称[] = {元素1,元素2…元素n};
int arr[] = {1,2,3};
数据类型 数组名称[长度n]; 数组名称[0] = 元素1; 数组名称[1] = 元素2; 数组名称[n-1] = 元素n;
int arr[3];
arr[0] = 1;
arr[1] = 2;
arr[2] = 3;
  • 获取数组中元素
数组名称[元素所对应下标];
arr[0];//即获得arr中第一个元素
  • 注意 :
    (1)数组的下标均以0开始;
    (2)数组在初始化的时候,数组内元素的个数不能大于声明的数组长度;
    (3) 如果采用第一种初始化方式,元素个数小于数组的长度时,多余的数组元素初始化为0;
    (4)在声明数组后没有进行初始化的时候,静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。
  • 获取数组长度
    用sizeof(arr)关键字获取整个数组所占字节,再由sizeof(arr[0])获取数组中一个元素所占的字节
    并相除得到数组长度
int length = sizeof(arr)/sizeof(arr[0]);

2.数组作为函数参数

数组可以由整个数组当作函数的参数,也可以由数组中的某个元素当作函数的参数:

  • 整个数组当作函数参数,即把数组名称传入函数中,例如:
#include <stdio.h>


void printArr(int arr[],int len)
{
	int i;
	for (int i = 0; i < len; i++)
	{
		printf("%d ",arr[i]);
	}

}
int main(int argc, char const *argv[])
{
	int len;
	int arr[4] =  {1,2,3,8};
	len = sizeof(arr)/sizeof(arr[0]);
	printArr(arr,len);
	return 0;
}

注意形参中不存在数组的概念,即便中括号约定了数组的大小也无效。
传递的是一个地址,是数组的首地址

  • 数组中的元素当作函数参数,即把数组中的参数传入函数中,例如:
void printArr(int a)
{
	printf("%d ",a);

}
int main(int argc, char const *argv[])
{
	int len;
	int arr[4] =  {1,2,3,8};
	printArr(arr[0]);
	return 0;
}

3.数组的应用(排序)

(1)冒泡法排序
以升序排序为例冒泡排序的思想:相邻元素两两比较,将较大的数字放在后面,直到将所有数字全部排序。

#include <stdio.h>
#include <stdlib.h>


int main(int argc, char const *argv[])
{
	int arr [] = {8,12,13,9};
	int i ,j;
	int size;
	int temp;
	size = sizeof(arr)/sizeof(arr[0]);
	printf("size = %d\n",size );

	printf("\n");

	for (int i = 0; i < size - 1; i++)
	{
		for (int j = 0; j < size - i-1; j++)
			{
				if(arr[j]>arr[j+1]){
					temp = arr[j+1];
					arr[j+1]=arr[j];
					arr[j] = temp;	
				}
			}	
	}
	printf("从小到大排序:\n");
	for (int i = 0; i < size; ++i)
	{
		printf("%d ",arr[i]);

	}


	return 0;
}

(2)选择法排序
以升序排序为例的选择法排序,将第一个元素与之后的元素比较,确定第一个元素为最小的元素,之后在确定第二个、第三个以此类推,进行排序。

#include <stdio.h>
#include <stdlib.h>


int main(int argc, char const *argv[])
{
	int arr [] = {8,12,13,9};
	int i ,j;
	int size;
	int temp;
	size = sizeof(arr)/sizeof(arr[0]);
	printf("size = %d\n",size );

	printf("\n");

	for (i = 0; i < size - 1; i++)
	{
		for (j = i+1; j < size; j++)
		{
			if(arr[i] > arr[j]){
				temp = arr[i];
				arr[i] = arr[j];
				arr[j] = temp;
			}
		}
	}

	printf("从小到大排序:\n");
	for (int i = 0; i < size; i++)
	{
		printf("%d ",arr[i]);

	}


	return 0;
}

4.二维数组

多维数组的定义格式是:

数据类型 数组名称[常量表达式1][常量表达式2][常量表达式n];
int arr[2][3] = {{1,2,3},{3,2,1}}; 
  • 多维数组的初始化与一维数组的初始化类似也是分两种:
数据类型 数组名称[常量表达式1][常量表达式2][常量表达式n] = {{1,,值n},{1,,值n},,{1,,值n}};
int arr[2][3] =  {{1,2,3},{3,2,1}}; 
数据类型 数组名称[常量表达式1][常量表达式2][常量表达式n]; 数组名称[下标1][下标2][下标n] =;
int arr[2][3];
arr[0][0] = 1;
arr[0][1] = 2;
.....
arr[1][2] = 3;
  • 多维数组初始化要注意以下事项:

采用第一种始化时数组声明必须指定列的维数。因为系统会根据数组中元素的总个数来分配空间,当知道元素总个数以及列的维数后,会直接计算出行的维数;
采用第二种初始化时数组声明必须同时指定行和列的维数。

  • 二维数组作为函数的参数时形参需要注意的地方:
    合法写法 :int arr[2][3] 或者 int arr[][3]
    不合法写法:int arr[][]
    说明: 二维数组是由若干个一维数组组成,数组是按行的形式存放的,在定义二维数组时,必须指定列的长度
    eg:
#include <stdio.h>


void printArr(int arr[][3])
{
	int i;
	int j;
	for (i = 0; i < 2; i++)
	{
		for (j = 0; j < 3; j++)
		{
			printf("%d ",arr[i][j]);
		}
		putchar('\n');
	}
}
int main(int argc, char const *argv[])
{
	
	int i,j;

	int arr[2][3] = {{1,2,3},{3,2,1}}; 
	for (int i = 0; i < 2; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			printf("%d ", arr[i][j]);
		}
		putchar('\n');
	}



	printArr(arr);
	return 0;
}

四、指针学习

1.指针概念

  • 1.1概念
    简单的来说,指针就是地址。我们口头上说的指针其实指的是指针变量指针变量就是一个存放地址的变量。
  • 1.2指针的大小

指针在32位机器下是4个字节,在64位机器下是8个字节。注:(指针的大小与类型无关)

  • 1.3指针的类型
    指针类型的作用是指针解引用访问,使用的访问权限是多少
    *当一个int 的指针+1,跳过4个字节;*当一个char 的指针+1,跳过1个字节。

同理,对于(指针±整数)类的题也是如上的原理。

#include <stdio.h>
#include <stdlib.h>
int main()
{   int a[2] = {10,1};
    int* p1;//定义指针变量
    p1=a;//取a的地址赋值给指针变量p

    char c[2] = {'a','c'};
    char *p2;
    p2 =c; 
    printf("a的地址是%p a的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
    printf("c的地址是%p a的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
    
    p1++;
    p2++;
    printf("a+1的地址是%p a+1的值为%d\n",p1,*p1);//*p表示取指针变量存放的值及地址中存放的值。
    printf("c+1的地址是%p c+1的值为%c\n",p2,*p2);//*p表示取指针变量存放的值及地址中存放的值。
    system("pause");
    return 0;
}

在这里插入图片描述
当一个int* 的指针+1,跳过4个字节;当一个char 的指针+1,跳过1个字节。
各种类型的大小:int类型四个字节,char类型一个字节,long,double,long long类型均为八个字节,long double类型为十六个字节

2. 野指针

2.1概念:野指针顾名思义,指针指向的位置不可知。
2.2野指针产生的原因:

  • 指针未被初始化
int * p;//(p就是一个野指针,及没有初始化的指针变量就是野指针)
  • 指针越界访问

  • 指针指向的空间释放

int* test( )
{
	int a = 5;
	return &a;
}
int main()
{
	int* p = test();
	*p = 10;
	return 0;
}

变量a的地址只在test()函数内有效,当把a的地址传给指针p时,因为出了test函数,变量a的空间地址释放,导致p变成了野指针。
2.3 野指针的规避

  • 小心越界
  • 及时把指针赋成空指针
  • 避免返回局部变量的地址
  • 使用指针前检查有效性

3.通过指针引用数组及指针的运算

1、数组元素的指针就是数组元素的地址(数组元素都在内存中占用存储单元,它们都有相应的地址)

2、数组名代表数组的首元素,引用其的方法为:
(1)下标法,如a[i]形式;
(2)指针法,如*(a+i)或*(p+i).

3、指针运算
(1)指针以指向一个数组元素时,可以进行以下运算
p+1,p-1,++p,p++,p- -,- -p
(p+1是指向同一数组中的下一个元素,加一代表的是加上一个数组元素所占用的字节数,同理可知p-1)
4、*(p+i) 或 * (a+i)是p+i 或a+i所指向的数组元素,即a[i],三者等价([ ]是变址运算符,将a[i]按a+i计算地址,然后找出此地址单元的值

#include<stdio.h>
int main()
{
	int a[5]={1,2,3,4,5};
	int *p;
	p=a;
	printf("%d\n",*(p+1));
	printf("%d\n",*(a+1));
	printf("%d",a[1]);
	return 0;
 } 

在这里插入图片描述

5、如果两个指针指向同一个数组,它们就可以相减,其结果为两个指针之间的元素数目。
p2 - p1,指的是元素的相对距离 如下例:
计算的结果为4,即a[0]与a[4]之间的距离为4

#include<stdio.h>
int main()
{
    int a[5]={1,2,3,4,5};
    int *p1,*p2;
    p1 = &a[0];
    p2 = &a[4];
    printf("p2-p1= %d\n",p2-p1);
    return 0;
 } 

在这里插入图片描述
6、指针常量不可以 ++
Eg:

int arr[3] = {1,2,3};
	int *p = arr;

	for(int i = 0;i<3;i++){
		printf("%d\n",*p++ );

	}
	for(int i = 0;i<3;i++){
		printf("%d\n",*arr++);//编译会报错,这里arr 是指针常量

	}

在这里插入图片描述

4.二维数组的地址认知

对于一个二维数组

int a[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
  • a 是二维数组名,a数组又包含了3行,即3个行元素:a [0],a[1],a[2]。而每一个行元素又是一个一维数组,它包含了4个元素:a[0][0],a[0][1],a[0][2],a[0][3]。
  • a[0],a[1],a[2]既然是一维数组名,C语言规定数组名代表数组首元素地址,因此,a[0]代表一维数组a[0]中第0列元素的地址,即&a[0][0],也就是说a[1]的值等于 &a[1][0] a[2]的值等于 &a[2][0]
  • a是父数组的地址 a[0] ,*a均表示子数组的地址
    在这里插入图片描述
#include <stdio.h>

int main(int argc, char const *argv[])
{
	
	int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
	printf("arr 的地址为%p ,arr+1的地址为%p\n",arr,arr+1); //arr+1 与arr 的地址相比较 偏移了16个字节
	printf("arr[0]是子数组的地址为 %p, arr[0]+1 的地址为%p\n",arr[0],arr[0]+1); //arr[0]+1 与arr[0]相比 偏移了4个字节
	printf("*arr 相当于 arr[0] 地址为 %p, 偏移1后的地址为%p\n", *(arr+0),*(arr+0)+1);//*(a+0) 等于a[0] *(a+1) 等于a[1]


	return 0;
}

在这里插入图片描述
遍历二维数组的三种形式:

#include <stdio.h>

int main(int argc, char const *argv[])
{
	
	int arr[3][4] = {{1,3,5,7},{9,10,11,13},{17,19,21,23}};
	int i;
	int j;
	for (i = 0; i < 3; i++)
	{
		for (j = 0; j < 4; j++)
		{
			printf("address:0x%p,data:%d\n",&(arr[i][j]),arr[i][j]);
			printf("address:0x%p,data:%d\n",arr[i]+j,*(arr[i]+j));
			printf("address:0x%p,data:%d\n",*(arr+i)+j,*(*(arr+i)+j));
			printf("===================================================\n");
		}
	}


	return 0;
}

二维数组a的相关指针
在这里插入图片描述

5.数组指针

  • 定义: int (*p)[3]; 数组指针强调的的类型,数组的个数,偏移值是整个数组的大小!
  • 说明:
    (1)数组指针,在其偏移时,偏移对应大小的数组
    (2)数组指针才是真正等同于二维数组名
int main()
{
	int ihang,ilie,data;
    int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
    int (*p) [4];//定义数组指针,指向数组的指针,这里指针偏移为 4*4 = 16个字节
    p = arr;
    printf("p = %p\n", p);
    printf("++p = %p\n", ++p);
	return 0;
}


由此可见数组指针+1之后,地址偏移了16字节

  • 应用:遍历整个二维数组
int getData(int(*p)[4],int hang,int lie)//数组指针 每次偏移的列需要定义出来 
{
	return *(*(p+hang) +lie);
}

int main()
{
	int ihang,ilie,data;
    int arr[3][4] = {{1,3,2,5},{4,9,6,2},{0,6,5,1}};
    //输入需要查找的行列:
    printf("请输入要查找的行号和列号:");
    scanf("%d%d",&ihang,&ilie);
    data =  getData(arr,ihang,ilie);
    printf("%d行%d列的元素为%d",ihang,ilie,data); 
	system("pause");
	return 0;
}

在这里插入图片描述

6.指针数组

  • 定义:
    一个数组,若其元素均为指针类型数据,称为指针数组,也就是说,指针数组中的每一个元素都存放一个地址,相当于一个指针变量。下面定义一个指针数组:
    int* p[3];//定义指针数组。多用指针数组存放函数指针。
    由于[]比*优先级高,因此p先与[3]结合,形成p[3]形式,这些显然是数组的形式,表示p数组又3个元素,然后再与p前面的*结合,表示次数组是指针类型的,每个数组元素都可指向一个整型变量
int main()
{
	int a = 0;
	int b = 1;
 
	int* p1 = &a;
	int* p2 = &b;
 
	int* arr[] = { p1,p2 };//指针数组
	int* arr[] = { &a,&b };//指针数组
 
	return 0;
}
  • 数组指针与指针数组的区别
int arr[5]               //名为arr的数组,数组里有5个元素,每个元素是int
int* p1[5]              //指针数组,数组里有5个元素,每个元素是int*
int (*p2)[5]            //数组指针,一个指向数组(里面有五个元素,每个元素是int)的指针                       
int (*p3[5])[5]        //p3[5]是一个有5个元素的数组,数组里每个元素是int(*)[5]

7.函数指针

  • 定义 :
    指向函数的指针int (*pf) (int,int),pf函数返回的类型是int
    如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址**(又称入口地址)称为函数的指针。**
    存放函数地址的变量。 函数名即为地址!
  • 注意:
    对于函数来说如:Add和&Add,意义和值都一样。这一定要区别于数组。
  • 函数指针示例(无返回值和参数)
void print()
{	
	printf("1234\n");		
}
int main()
    {
	void (*p)();
    p=print;
	print();
	//通过函数指针访问函数方法1,直接用函数指针名字加()
    p();
    //方法2,用*去函数指针所指的函数内容
    (*p)();
	system("pause");
	return 0;
}
  • 带参数与返回值的函数指针:
#include <stdio.h>
#include <stdlib.h>
int test(int num)
{
	return ++num;

}
int main()
{

	int (*ptest) (int nun); //返回值类型为int,且带有int类型的参数
	ptest = test;

	printf("%d\n", (*ptest)(10));

	
	return 0;
}
  • 函数指针练习
    (1)输入不同操作命令,调用不同函数:
#include <stdio.h>
#include <stdlib.h>
int getMax(int data1,int data2)
{

	return data1 > data2 ? data1:data2;  
}
int getMin(int data1,int data2)
{

	return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{

	return data1+data2;
}

int funHandler(int data1,int data2,int (*pfun)(int ,int))
{

	int ret;
	ret = (*pfun)(data1,data2);
	return ret;
}

int main()
{

	int cmd;
	int data1,data2;
	int ret;

	int (*pfun)(int ,int);
	printf("请输入要运算的数组:\n");
	scanf("%d %d",&data1,&data2);

	printf("请输入要执行的操作(1:求最大值,2:求最小值,3:求和):\n");
	scanf("%d",&cmd);

	switch(cmd){
		case 1: pfun = getMax;
			break;
		case 2: pfun = getMin;
			break;
			
		case 3: pfun = getAdd;
			break;
		default:
			printf("输入错误!\n");
			exit(-1);
	}

	ret= funHandler(data1,data2,pfun);
	printf("ret = %d\n",ret);


	return 0;
}

(2)结合指针数组对上例改写,将输入的数据进行求最大值,最小值,求和运算

#include <stdio.h>
#include <stdlib.h>
int getMax(int data1,int data2)
{

	return data1 > data2 ? data1:data2;  
}
int getMin(int data1,int data2)
{

	return data1 < data2 ? data1:data2;
}
int getAdd(int data1,int data2)
{

	return data1+data2;
}

int main()
{

	int cmd;
	int data1,data2;
	int ret;

	int (*pfun[3])(int ,int) = {getMax,getMin,getAdd};//定义函数指针数组,并进行初始化


	printf("请输入要运算的数字:\n");
	scanf("%d %d",&data1,&data2);

	printf("输入数字的最大值,最小值,和分别为:\n");
	for (int i = 0; i < 3; i++)
	{	
		ret = (*pfun[i])(data1,data2);
		printf("%d\n",ret);
	}
	

	return 0;
}

8.指针函数

  • 定义:
    一个函数可以返回一个整型值、字符值、实型值等,也可以返回指针型的数据,即地址
    例如:int * a(int x,int y);
    *a两侧没有括号,在a的两侧分别为运算符和()运算符。而()优先级高于,因此a先与()结合,显然是函数形式。且函数前面有一个*,表示此函数时指针型函数。
  • 指针函数练习:
    (1)例有a个学生,每个学生有 b门课程的成绩。要求在用户输人学生序号以后,能输出该学生的全部成绩。用指针函数来实现。
#include <stdio.h>

int *getScore(int pos,int (*pstu)[4])
{
	int *p;
	/*将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节) 
	 从而找到对应行地址,并将行地址转换为一维数组地址*/
	p = (int *)(pstu + pos); 
	return p;
}

int main(int argc, char const *argv[])
{
	


	 int scores[3][4] = {	{90,91,92,99},
						 	{89,88,89,100},
						 	{87,85,78,100}
						 };
	int pos;
	int *ppos;
	printf("请输入要查看学生的序号(0,1,2):\n");
	scanf("%d",&pos);
	ppos = getScore(pos,scores);

	for (int i = 0; i < 4; i++)
	{
		
		printf("%d ",*ppos++);
	}
	return 0;
}

(2)对上例中的学生,找出其中有不及格的课程的学生及其学生号。

#include <stdio.h>

int *getScore(int pos,int (*pstu)[4])
{
	int *p;
	/*//将二维数组指针(偏移16字节) 转换为 int *类型的指针(偏移4字节) 
	 从而找到对应行地址,并将行地址转换为一维数组地址*/
	p = (int *)(pstu + pos); 
	return p;
}
int *searchFail(int (*pstu)[4])
{
	int *p;
	
	for (int i = 0; i < 4; i++)
	{
		//printf("%d ",*(*(pstu+0)+i));
		if(*(*pstu+i) < 60){
			p = pstu;
		}
	}
	return p;

}
int main(int argc, char const *argv[])
{

	 int scores[3][4] = {	{90,91,92,99},
						 	{89,59,89,100},
						 	{87,85,78,100}
						 };
	int pos;
	int *ppos;
	int *failPos;
	printf("请输入要查看学生的序号(0,1,2):\n");
	scanf("%d",&pos);
	ppos = getScore(pos,scores);

	for (int i = 0; i < 4; i++)
	{
		
		printf("%d ",*ppos++);
	}

	putchar('\n');
	for (int i = 0; i < 3; i++)
	{
		
		failPos = searchFail(scores+i);
		if(failPos == *(scores+i)){

			printf("第%d个学生不及格\n",i+1);
			for (int j = 0; j < 4; j++)
			{
				printf("%d ",scores[i][j]);
			}
		}
	}

	return 0;
}

9.二级(多级)指针

  • 定义:
    多级指针就是保存的是指针变量的地址
  • 用途:
    通过函数调用来修改调用函数指针指向,与通过函数调用修改某变量的值一样
  • 示例:用过二级指针修改一级指针的内容。
#include <stdio.h>

void getScore(int pos,int (*pstu)[4],int **ppos)//这里int **ppos为二级指针,接收主函数传来的指针的地址
{	

	*ppos = (int *)(pstu + pos); //这里修改主函数传入的指针的地址,并访问其保存的指针(通过*ppos)的方法,达到改变其指向的新地址
}


int main(int argc, char const *argv[])
{
	


	 int scores[3][4] = {	{90,91,92,99},
						 	{89,88,89,100},
						 	{87,85,78,100}
						 };
	int pos;
	int *ppos;
	printf("请输入要查看学生的序号(0,1,2):\n");
	scanf("%d",&pos);
	getScore(pos,scores,&ppos);//这里&ppos 传入的是指针ppos的地址 即二级指针

	for (int i = 0; i < 4; i++)
	{
		printf("%d ",*ppos++);
	}
	return 0;
} 

10.各种指针定义:

1.一个指向指针的指针,它指向的指针指向一个整型数 : int **a;
2.一个有10个整型数的数组 :int a[10];
3.一个有10个指针的数组,每个指针指向一个整型数: int * a[10];
4.一个指向有10个整型数的数组指针: int (*a)[10];
5.一个指向指针的指针,被指向的指针指向一个有10个整型数的数组:int (**a)[10];
6.一个指向数组的指针,该数组有10个整型指针: int *(*a)[10];
7.一个指向函数的指针,该函数有一个整型参数并返回一个整型数: int (*a)(int);
8.一个有10个指针的数组,每个指针指向一个函数,该函数有一个整型参数并返回一个整型数: int (*a[10])(int);
9.一个函数的指针,指向的函数类型是有两个整型参数并返回一个函数指针的函数,返回的函数指针指向有一个整型参数且返回整型数的函数:
指向有两个整型参数的函数指针: (*a)(int,int);
返回的函数指针 是有一个整型参数且返回整型的函数:int *() (int)
将定义的函数指针放到返回值类型(返回值类型为函数指针):int *((*a)(int,int))(int);

11.无类型指针

  • 定义:
    void并不是指没有返回值 而是指的无类型。
    malloc(size-t);函数:返回值为void*(无类型指针) 参数中的size-t指的是开辟了这么多字节的空间。
    定义:
    int *parry =(int*) malloc(n*sizeof(int));
    malloc()调用后的返回值为无指针类型 把它强制转换成(int*)类型相当于定义了一个整数型的指针变量
    int *parry =(int*) malloc(12); 相当于int parry[3];
  • 用法:可动态申请数组
	int n;
	printf("输入数组的个数%d",n);
    scanf("%d",&n);
	int a[n];
    //这种方式不合法可以用malloc函数定义:
	int*parray=(int*)malloc(n*sizeof(int));

12.内存泄漏

  • 定义:
    内存泄漏:程序刚运行很好,跑一段时间后出现错误。
    int *p =malloc(1024)
    malloc申请空间,系统不会主动释放,若是Linux系统的话,程序结束后会回收空间。malloc可能会失败,要对返回值做判断:
int *p =malloc(1024);
if(p==NULL){
	printf(“申请内存失败”);
	exit(-1);//结束进程,-1代表出现异常。
}
  • 如何避免
    1.循环中有没有一直申请空间,避免这种情况发生
    2.有没有合理释放函数 用free()函数进行释放空间free(p);p=NULL;释放后注意p变为了野指针,应当给它初始化!

五、字符学习

1.字符类型

char是一种整数,也是一种特殊的类型——字符。用单引号表示的字符字面量:‘a’,'1’也是一个字符,printf和scanf里面用%c来输入输出字符。

2.字符读取

1.scanf()函数

  • 用法
scanf("%d",&p);

%d说明我们现在要读入一个整数了,scanf这个函数会读入下一个整数,读到的结果赋值给指定变量,要注意指定变量前面的&
如果要读入输入的数据,就必须严格按照""中的格式输入,如:

int main()
{
 int a,b;
 scanf("%d,%d",&a,&b);
 printf("%d %d\n",a,b );
    return 0;
}

在这里插入图片描述
若不严格按照格式输入,则会导致接收错误

  • scanf语句的原理
    scanf并不会直接从我们的键盘输入中读取数据,我们从键盘输入的数据先存储在输入缓冲区中,当按下回车键后,即输入缓冲区遇到回车符时,程序才会在输入缓冲区中读取数据,scanf读取到空格、tab或者回车符的时候停止读取。
    scanf语句读取完数字后,会把后面的空格也读取掉。如果后面有变量来接收,读取后就会把空格赋值给对应的变量,如果没有变量来接收,那么仅仅是读取,不会进行赋值操作。值得注意的是,scanf只有在读取完数字后会继续把后面的空格读取掉
    如下例:
int main()
{
    char word[8];
    scanf("%s",word);
    printf("%s\n",word);
    
    return 0;
}

在这里插入图片描述
这说明读取完字符o后,并没有将后面的空格读取出来,遇到空格就直接停止读取了

2.getchar和putchar函数用法
(1)getchar函数
函数格式: int getchar()
所在头文件: <stdio.h>

函数功能: 从输入缓冲区中读取单个字符

函数返回值: 成功时为获得的字符,失败时为 EOF 。
(2)putchar函数
函数格式: int putchar(int ch)

所在头文件: <stdio.h>

函数功能: 输出字符到标准输出上,等价于 putc(ch, stdout)

函数参数: ch,表示要写入的字符,可以是字符常量,转义字符,可以是介于0~ 127之间的一个十进制整型数(包含0和127,超过127就不是ASCII码了),当参数为一个介于0~127(包括0及127)之间的十进制整型数时,它会被视为一个ASCII代码,输出该ASCII代码对应的字符,也可以是一个字符变量。

参数为是一个字符,但参数却是int型,这表示1字节的字符存在4字节大小的内存中。

或者换个理解方式,字符对应的ASCII码值用int型参数来接收。

函数返回值:

成功时,返回参数的ASCII码值。

失败时,返回 EOF 并设置 stdout 上的错误指示器。
3.getchar和putchar函数工作原理:
getchar()用于从键盘输入中读取单个字符,当我们连续输入多个字符时,第一个getchar()读取第一个字符,后续的getchar()依次读取后续的字符,当getchar()读取到EOF或者读取到回车时停止执行。

while ((ch = getchar()) != EOF) {

		putchar(ch);

	}

#include<stdio.h>

int main()
{
	char ch = ' ';

	while ((ch = getchar()) != EOF) {

		putchar(ch);

	}

	return 0;
}

我们输入abc按下回车后,
输入缓冲区中的内容:abc\n
由于遇到\n,因此getchar开始从输入缓冲区中读取字符,先读取a,满足输出while循环条件,可以输出,先存储到输出缓冲区中,然后读取b,满足输出while循环条件,可以输出,先存储到输出缓冲区中,然后读取c,满足输出while循环条件,可以输出,先存储到输出缓冲区中,最后读取\n,满足输出while循环条件,可以输出,先存储到输出缓冲区中。
此时输出缓冲区中的内容:abc\n
由于输出缓冲区遇到了\n,所以进行一次系统调用,将输出缓冲区的内容直接输出到标准输出上。
在这里插入图片描述

3.字符串

1.自符串的定义:
char* str=” Zhang ”;
相当于char str []=”Zhang”;
这里直接使用指针因为数组的名称相当于首元素的地址。

  • 注意:
    上述方法定义 char* str=” Zhang ”; 是字符串的常量,内容不可以修改
    若定义为: char str []=”Zhang”; 则为字符串变量可以修改!
  • 调用时:
    printf(“%s”,str);//调用时直接用%s占位符即可。
  • 字符串在内存中的存储方式:
    字符串在存储时,除了有效字符以外,还会自动在后面补一个’\0’的结束标志。
    2.strlen();和sizeof();的区别
  • 计算字符串有效字符的个数用函数 strlen();而不是sizeof();
  • sizeof();是C语言得关键字,而strlen();是函数
strlen();在计算字符大小时遇到’\0’就停止了计数。
sizeof 计算字符数组大小时计算全部数组大小,即:
char data [128] = “hello”;
sizeof(data) = 128;
strlen(data) = 5;

3.注意出现野指针造成非法内存访问,出现段错误。
形如:

char*pstr;
scanf(%s”,pstr);
puts(pstr);

便会造成非法内存访问。定义出来的pstr 成为了一个野指针,指针变量里面所存放的东西未知且系统没有给空间。
解决方法:
方法1:(直接用字符数组并在内存放128个’\0’)

`char pstr[128]=’\0;`//申请空间并做了初始化,把每个元素都初始化为’\0’
scanf(%s”,pstr);
puts(pstr);

方法2:(用malloc函数开辟空间)

char*pstr=(char*)malloc(128)

4.malloc 动态开辟内存空间
与malloc 相关的函数
(Linux下)

void *malloc(size_t size);
void free(void *ptr);
void *calloc(size_t nmemb, size_t size);
void *realloc(void *ptr, size_t size);

void *malloc(size_t size); 分配所需的内存空间。并返回一个指向它的指针

void free(void *ptr); 释放,防止内存泄露 当内存不再使用的时候,应使用free()函数将内存块释放掉。

void *calloc(size_t nmemb, size_t size);
calloc(10 , sizeof(int)) calloc在返回在堆区申请的那块动态内存的起始地址之前,会将每个字节都初始化为0
void *realloc(void *ptr, size_t size); 尝试重新调整之前malloc或calloc所分配的指针所指向内存块的大小

5.字符串的操作函数:(调用函数时需要包含文件#include<string.h>)

(1)puts(str);//输出字符串函数。(此函数可以自动加换行\n)

#include<stdio.h>
#include<string.h>


int main()
{
     char * str = "hello";
     puts(str);

    return 0;
}

在这里插入图片描述

(2)gets(str);

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    char *str;
    str = (char *)malloc(32);
    gets(str);
    printf("str:%s\n",str);
    return 0;
}

在这里插入图片描述

(3)void *memset(void *s, int c, unsigned long n);
第一个参数void* mem_loc:已开辟内存空间的首地址,通常为数组名或指针,由于其为void*,故函数能为任何类型的数据进行初始化。

第二个参数int c:初始化使用的内容,取器低字节部分。

第三个参数size_t n:需要初始化的字节数。

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

int main()
{
    char char_arr[5];
    int int_arr[10];
    int* int_malloc_ptr = (int*)malloc(sizeof(int) * 15);

    memset(char_arr, 0, sizeof(char_arr));          // 数组名可以使用sizeof(arrName), 1 * 5 = 5
    memset(int_arr, 0, sizeof(int_arr));            // 4 * 10 = 40
    memset(int_malloc_ptr, 0, sizeof(int) * 15);    // 4 * 15 = 60

    printf("%s\n",char_arr);

    return 0;
}

(4)strcpy(str1,str2);字符串拷贝
把str2的内容拷贝到str1中

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

int main()
{
    char *str1 = "Hello World";
    char *str2;
    strcpy(str2,str1);
    printf("str2:%s\n",str2);
    return 0;
}

在这里插入图片描述

(5)strncpy(str1,str2,int n);把str2的内容拷贝到str1中,拷贝n个字符

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

int main()
{
    char *str1 = "Hello World";
    char *str2;
    strncpy(str2,str1,6);
    printf("str2:%s\n",str2);
    return 0;
}

在这里插入图片描述

(6)strcat(str1,str2);//拼接函数 把str1和str2接到一起。

注意
由于要改变字符串需要将字符串定义为数组形式

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

int main()
{
    char str1[] = "Hello ";
    char str2[] = "World";
    strcat(str1,str2);
    printf("str1:%s\n",str1);
    return 0;
}

在这里插入图片描述

(7)strlwr(str); 将一段字符转为小写
注意定义str时只能用数组定义

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

int main()
{
    char str1[] = "HELLO";
    strlwr(str1);
    printf("str1:%s\n",str1);
    return 0;
}

在这里插入图片描述

(8)strupr(str);将一段字符转为大写。

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

int main()
{
    char str1[] = "hello";
    strupr(str1);
    printf("str1:%s\n",str1);
    return 0;
}

在这里插入图片描述

(9)strtok(str1,”,”);字符切割 将str1以,为结束标志切割
注意只能分割出来第一个字符串若想继续分割则要继续调用strok()调用而且要把目标字符串改成NULL strok(NULL,”,”);

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

int main()
{
    char str1[]="hello,world";
    char*p=NULL;
    p=strtok(str1,",");
    puts(p);//注意只能分割出来第一个字符串若想继续分割则要继续调用strok()调用而且要把目标字符串改成NULL  strok(NULL,”,”);
    return 0;
}

在这里插入图片描述

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

int main()
{
    char str[]="hello,world";
    char*p=NULL;
    p=strtok(str,",");
    puts(p);
    p=strtok(NULL,",");
    puts(p);
    return 0;
}

在这里插入图片描述

(10)strchr(); 检索字符在一个字符串中出现的位置

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

int main()
{
    char*str="hello world";
    char c='w';
    char*p=NULL;
    //str 要被检索的字符串,c在str中要搜索的字符,返回值 返回字符串中第一次出现该字符的指针。若字符串中不包含该字符则返回NULL。
    p=strchr(str,c);
    puts(p);
    system("pause");
    return 0;
}

在这里插入图片描述可见得到了第一次出现的’w’指针,并返回"world"字符串

(11)strstr();检索子串在一个字符串中出现的位置,并返回子串指针。

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

int main()
{
    char*str="hello world!";
    char*p=NULL;
    char*substr="wor";
    p=strstr(str,substr);
    if(p==NULL)
    {
        printf("没有找到\n");
        
    }
    else{
    puts(p);
    }
    return 0;
}

在这里插入图片描述

(12)int ret =strcmp(char *s1,char*s2);

  • 功能:比较两个字符串

(1). 如果两个字符串相等,则返回0;

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

int main()
{
    char* str1 = "helloworld";
    char* str2 = "helloworld";
    int ret;
    ret = strcmp(str1,str2);
    printf("%d\n",ret);
    return 0;
}

在这里插在这里插入图片描述
入图片描述

(2). 若str1大于str2,返回一个正数,这个正数不一定是1;

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

int main()
{
    char* str1 = "helloworld";
    char* str2 = "hellow!";
    int ret;
    ret = strcmp(str1,str2);
    printf("%d\n",ret);
    return 0;
}

在这里插入图片描述

(3). 若str1小于str2,返回一个负数,这个数不一定是-1

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
int main()
{
    char* str1 = "helloworld!";
    char* str2 = "hello!";
    int ret;
    ret = strcmp(str2,str1);
    printf("%d\n",ret);
    return 0;
}
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int main()
{
    char* str1 = "hello world";
    char* str2 = "helloworld";
    int ret;
    ret = strcmp(str1,str2);
    printf("%d\n",ret);
    return 0;
}

在这里插入图片描述

  • 注意:若字符串中存在空格,则以空格前的字符串进行比较:

(13)断言函数:
#include <assert.h>
assert的作用是现计算表达式expression,如果其值为假(0)则会打印错误消息终止运行:

6.自己实现的字符串操作函数

(1)strcpy

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
char* myStrcpy(char *des_str,char *src_str)
{

    assert(des_str != NULL&&src_str != NULL);
	char *temp = des_str;
    while(*src_str != '\0'){
		*des_str++ = *src_str++;
    
    }
    *des_str = '\0';
    
    return temp;



}

int main()
{
	char * str = "hello world!";
    char str1[128] = {'\0'};
    myStrcpy(str1,str);
    printf("%s\n",str1);
    
	system("pause");
	return 0;
}

(2)strcat

char *myStrcat(char *des_str,char *src_str)
{
    assert(des_str!=NULL&&src_str!=NULL);
    char *temp = des_str;
    while(*des_str != '\0'){
        des_str++;
    }
    
    while((*des_str++ = *src_str++)!='\0');
    *des_str = '\0';

    return temp;

}

int main()
{
    char *str = "world!";
    char str1[32] = "hello ";
    char *p2;
   
    p2 = myStrcat(str1,str);
    printf("%s\n",p2);
    
    system("pause");
    return 0;
}

(3)strcmp

int myStrcmp(char *str1,char *str2)
{
    int str1Len;
    int str2Len;
    while(*str1!='\0'){
        str1++;
        str1Len++;
    }
    while(*str2!='\0'){
        str2++;
        str2Len++;
    }
    if(str1Len < str2Len){
        return -1;
    }else if (str1Len == str2Len)
    {
        return 0;
    }else if(str1Len > str2Len){
        return 1;
    }

}

六、结构体

1.结构体介绍

在实际问题中,一组数据往往具有不同的数据类型;例如在学生信息登记表中,姓名为字符型,学号为整型或字符型,年龄为整型,性别为字符型,成绩为整型或实型。因为数据类型不同,显然不能用一个数组来存放。
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体(struct)指的是一种数据结构,是C语言中聚合数据类型(aggregate datatype)的一类。结构体可以被声明为变量、指针或数组等,用以实现较复杂的数据结构。结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问。

  • 结构体定义
    自定义的类型。注意:结构体定义中,结构体右括号有;
struct student
{	
	int score;
	char name[128];
};
  • 结构体的使用
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
struct student
{   
    int score;
    char name[128];
};
int main()
{

    struct student stu1={98,"zhangSan"};
    printf("name:%s score=%d\n",stu1.name,stu1.score);

    return 0;
}

在这里插入图片描述

  • 当对结构体里的变量赋值
    注意在C语音中不能用test.name=“张三”;这种方法!
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
struct student
{   
    int score;
    char name[128];
};
int main()
{

    struct student stu1={98,"zhangSan"};
    struct student test;
    test.score=100;
    strcpy(test.name,"张三");//注意在C语音中不能用test.name="张三";这种方法!
    printf("name:%s score=%d\n",test.name,test.score);
    return 0;
}

  • 使用结构体的注意事项
    最好不要以这种方式进行指针定义,这样就定义了一个野指针!
struct DATA{
	char*p;
};

应该以这种方式定义。

struct DATA{
	char p[128];
};

如果以第一种方法定义的话 运用时要注意初始化!

struct DATA d;
d.p=(char*) malloc(128);//申请空间
memset(d.p,’\0,128);//初始化

2.结构体指针

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
struct student
{   
    int score;
    char name[128];
};
int main()
{

    struct student *p;
    struct student stu1;
    stu1.score=100;
    strcpy(stu1.name,"huangSi");

    printf("name:%s score=%d\n",test.name,test.score);
    p=(struct student*)malloc(sizeof(struct student));//要给指针p开辟空间!!
    p->score=98;
    strcpy(p->name,"zhangSan");
    printf("%s的分数为%d\n",p->name,p->score);
    free(p);//指针是存放地址的变量,之前指向的malloc开辟的地址,之后指的是stu1的地址。
    //因此为了防止内存泄漏,先把p原先指向的malloc所开辟的空间释放。
    
    p=&stu1;
    printf("%s的分数为%d\n",p->name,p->score);
    return 0;
}

定义结构体指针 struct student *p;不过是一个野指针。
如果定义结构体指针,就不能用.运算符访问结构体里的内容,应该用->来访问
在上例中:
指针p是存放地址的变量,之前指向的malloc开辟的地址,之后指的是stu1的地址。
因此为了防止内存泄漏,先把p原先指向的malloc所开辟的空间释放。 free(p);
再将p指向stu1. p=&stu1;

3.结构体大小的计算:

计算法则
1.结构体成员的偏移量,是其成员的大小的整数倍(0是被认为是所有数的整数倍)
2.结构体的大小必须是所有成员大小的整数倍
3.结构体中数组,结构体只计算其大小,不参与1、2的法则判定,

1.结构体成员的偏移量,是其成员的大小的整数倍(0是被认为是所有数的整数倍)

struct T1{	//偏移量	大小
	char a;	//0			 1
	char b; //1			 1
	int c;  //2			 4	(空出2个字节)
			//结构体大小: 1+1+2+4=8
};

对应char a;偏移量大小为0,本身大小为1,
对于char b;偏移量大小为1,本身大小为1,均符合条件
对于int c;偏移量大小为2,本身大小为4,故需要在多偏移2字节
最终结构体大小为8字节
2.结构体的大小必须是所有成员大小的整数倍

struct s2{   //偏移量大小
	char ch1;//0          1
	int i;   //1          4
 	char ch2;//8          1
};

对于ch1;偏移量大小为0,本身大小为1,
对于i ; 偏移量大小为1,本身大小为4,不满足第一个条件,需要再多偏移3,
对于ch2;偏移量为8,本身大小为1
此时结构体大小为1+4+3+1 = 9,不满足结构体的大小必须是所有成员大小的整数倍条件,故还需补充到12个字节满足条件
3.结构体中数组,结构体只计算其大小,不参与1、2法则判定
(1)结构体中包含数组

struct T1{		//偏移量	大小
	char a;		//0			 1
	int b;  	//1			 4	(空出3个字节)
	char c[13]; //8			13	(数组不包含在法则内,还是13个字节无空出字节)
			//结构体大小: 1+4+3+13=21, 根据法则,21不是4的整数倍,所以为24
};

前两个变量还是一样的计算,字符不用考虑法则1,2,所以直接加上数组的大小,同时不考虑数组的偏移量与大小的关系

(2)结构体中包含结构体
结构体内的结构体定义与否会影响整个和结构体大小,但任然不参与法则判定
①.对于解结构内的结构体,如果内部结构体声明了结构体却没定义,那么其大小不计算在整个结构体的大小内

struct T1{		//偏移量	大小
	char a;		//0			 1
	int b;  	//1			 4	(空出3个字节)
	struct T2{	//声明无大小
		char c;	
		int d; 	
	};
	float e;	//8			 4
				//1+4+3+4 = 12
};

如果不定义结构体T2变量,直接打印sizeof(struct T1),在Linux下会警告
②.前面a,b同理,结构体类型的成员变量继续偏移算结构体总大小,此时T2声明并定义了变量temp,易得T2结构体大小为8,e偏移了16字节,大小为4没有多余空出字节,所以大小为1+4+3+8(结构体)+4=20;

struct T1{		//偏移量	大小
	char a;		//0			 1
	int b;  	//1			 4	(空出3个字节)
	struct T2{				//整各大小为8
		char c;	//0			 1
		int d; 	//1			 4	(空出3个字节)
	}temp;
	float e;	//16			 4
				//1+4+3+1+4+3+4 = 20
};

3.对齐方式

1.数据对齐
现在计算机内存空间都是按照byte划分的,理论上来说任何变量从任何地址开始都可以。但是为了配合硬件,就需要进行数据对齐,将各个类型的数据按照一定的规则在空间进行排布,而不是顺序的一个个排放。
当我们定义一个变量时,编译器会按默认的地址对齐来一块存储地址空间。比如定义了一个int型的变量,那么改变量会4字节或4字节的整数倍对齐,就是存放在地址能整除4的地址空间内(PS:0x0004这样,能整除4的)。
2.数据对齐规则
这里引入三个概念:自身对齐值,指定对齐值,有效对齐值。

1)自身对齐值:数据类型的自身对齐值,如char的自身对齐值是1,int是4;
2)指定对齐值:编译器或者程序员指定的(#pragma pack (value))默认对齐值。
PS:结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3)有效对齐值:自身对齐值和指定对齐值中较小的那个值。
对齐有两个规则:
1、不但结构体的成员有有效对齐值,结构体本身也有对齐值,这主要是考虑结构体的数组,对于结构体或者类,要将其补齐为其有效对齐值的整数倍。结构体的有效对齐值是其最大数据成员的自身对齐值;
2、存放成员的起始地址必须是该成员有效对齐值的整数倍。
例:
指定对齐:

#pragma() pack(4)//指定向4对齐,最大是8,没有超过最大的double c 8。
struct s1{
	char a;
	int b;
	float c;
	double d;
};//这种情况按照4对齐所以此结构体的大小是20  

假如结构体起始地址是0x0000,
成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;
成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存放起始地址是0x0004,占四个个字节(0x0004-0x0007);
成员c的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0008是4的整数倍,故c存放起始地址是0x0008,占四个字节(0x0008-0x000B);
成员d的自身对齐值8,指定对齐值4,所以有效对齐值是4,地址0x000C是4的整数倍,故d存放起始地址是0x000C,占8个字节(0x000C-0x0014);
此时结构体A的有效对齐值是其指定对齐值,为4,故结构体s1的有效对齐值是4,下载A占字节数为0x0000-0x0014,20个字节是4的整数倍,所以不需要额外补齐了。最后结构体s1占总字节数为20。
ps:0x0001、0x0002、0x0003内存空间都空着

#pragma() pack(10)//指定向10对齐,最大是8,超过最大的double c 8。
sstruct s2{
char a;
int b;
float c;
double d;
};//这种情况下还按照8对齐所以结构体大小是24。

假如结构体起始地址是0x0000,
成员a的自身对齐值1,指定对齐值4,所以有效对齐值是1,地址0x0000是1的整数倍,故a存放起始地址是0x0000,占一个字节;
成员b的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0004是4的整数倍,故b存8放起始地址是0x0004,占四个个字节(0x0004-0x0007);
成员c的自身对齐值4,指定对齐值4,所以有效对齐值是4,地址0x0008是4的整数倍,故c存放起始地址是0x0008,占四个字节(0x0008-0x000B);
成员d的自身对齐值8,指定对齐值8,所以有效对齐值是8,地址0x00010是8的整数倍,故d存放起始地址是0x0010,占8个字节(0x0010-0x0017);
此时结构体A的有效对齐值是其指定对齐值,为8,故结构体s2的有效对齐值是8,下载A占字节数为0x0000-0x0017,24个字节是8的整数倍,所以不需要额外补齐了。最后结构体s2占总字节数为24。
ps:0x0001、0x0002、0x0003、0x000C、0x000D、0x000E内存空间都空着

七、联合体 union

1.联合体介绍

在C语言中,变量的定义是分配存储空间的过程。一般的,每个变量都具有其独有的存储空间,那么可不可以在同一个内存空间中存储不同的数据类型(不是同事存储)呢?

答案是可以的,使用联合体就可以达到这样的目的。联合体也叫共用体,在C语言中定义联合体的关键字是union。

  • 联合体定义
union 联合名
{
成员表
};

成员表中含有若干成员,成员的一般形式为: 类型说明符 成员名。其占用的字节数与成员中最大数据类型占用的字节数。

  • 联合体的使用
    (1)联合体变量的定义
    方法一:先创建模板,再定义变量
// 创建联合体模板union perdata
union perdata
{
    int Class;
    char Office;
};
// 使用该联合体模板创建两个变量a, b
union perdata a,b;

方法二:同时创建模板和变量

// 创建联合体模板union perdata的同时定义两个变量a、b
union perdata
{
    int Class;
    char Office;
}a,b;

方法三:省略联合体名

union
{
    int Class;
    char Office;
}a,b;

方法四:使用typedef

// 联合体模板union perdata重新命名为perdata_U
typedef union perdata
{
    int Class;
    char Office;
}perdata_U;
// 使用新名字perdata_U创建两个变量a, b
perdata_U a,b;

(2)联合体初始化
联合体的初始化与结构体不同,联合体只能存储一个值。

perdata_U a;
a.Class = 10;
perdata_U b = a;				/* 1、把一个联合初始化为另一个同类型的联合; */
perdata_U c = {20};				/* 2、初始化联合的第一个成员; */
perdata_U d = {.Office = 30};   /* 3、根据C99标准,使用指定初始化器。 */

2.联合体应用

根据输入人的职位存储不同的信息,当输入为教师时,用char 类型存储其教授的科目,当输入为学生时,用int 类型存储其所在班级:

#include <stdio.h>


struct  Person
{
	char name[32];
	int age;
	char addr[12];
	char position;
	union {

		int class;
		char subject[12];
	} pesonMes;
};

int main(int argc, char const *argv[])
{
	struct Person p [2];
	int i;

	for (i = 0; i < 2; i++)
	{
		printf("请输入人员的职位(s:学生,t:教师):\n");
		scanf("%c",&(p[i].position));
		if(p[i].position == 's'){

			printf("请输入学生的名字:\n");
			scanf("%s",&(p[i].name));
			printf("请输入学生的班级:\n");
			scanf("%d",&(p[i].pesonMes.class));
		}
		else if(p[i].position == 't'){

			printf("请输入教师的名字:\n");
			scanf("%s",&(p[i].name));
			printf("请输入教师所教的科目:\n");
			scanf("%s",&(p[i].pesonMes.subject));
		}else {

			printf("err!\n");
			break;
		}
		getchar();
	}

	for (int i = 0; i < 2; i++)
	{
		if(p[i].position == 's'){
			printf("学生姓名:%s,学生班级:%d\n",p[i].name,p[i].pesonMes.class);
		}else if(p[i].position == 't'){
			printf("教师姓名:%s,教师班级:%s\n",p[i].name,p[i].pesonMes.subject);
		}
	}

	return 0;
}

八、 枚举类型 enum

1.枚举类型介绍

1.概念 :枚举是C语言中的一种基本数据类型,它可以让数据更简洁,更易读。

  • 枚举语法定义格式为:
enum 枚举名
{
枚举元素1,枚举元素2,…… //注意,各元素之间用逗号隔开
}; //注意,末尾有分号
  • 枚举的用途
    枚举在C语言中其实是一些符号常量集。直白点说:枚举定义了一些符号,这些符号的本质就是int类型的常量,每个符号和一个常量绑定。这个符号就表示一个自定义的一个识别码,编译器对枚举的认知就是符号常量所绑定的那个int类型的数字。
    一般情况下我们都不明确指定这个符号所对应的数字,而让编译器自动分配。

编译器自动分配的原则是:从0开始依次增加。如果用户自己定义了一个值,则从那个值开始往后依次增加。

C语言为何需要枚举?

C语言没有枚举是可以的。使用枚举其实就是对一些数字进行符号化编码,这样的好处就是编程时可以不用看数字而直接看符号。符号的意义是显然的,一眼可以看出。而数字所代表的含义除非看文档或者注释。

这么看来,宏定义也能实现呀!要专门弄个枚举类型干嘛?

宏定义和枚举有内在联系,宏定义和枚举经常用来解决类似的问题,他们俩基本相当可以互换,但是有一些细微差别。

  • 宏定义和枚举的区别:
    1、枚举是将多个有关联的符号封装在一个枚举中,而宏定义是完全散的。也就是说枚举其实是多选一,而且只能在这里面选,有时候有效防止其他不符数据的输入。
    2、什么情况下用枚举?当我们要定义的常量是一个有限集合时(譬如一星期有7天,譬如一个月有31天,譬如一年有12个月····),最适合用枚举。(其实宏定义也行,但是枚举更好)
    3、不适合用枚举的情况下(比如定义的常量符号之间无关联,或者无限的)用宏定义。
    宏定义示例:
#define MON  1
#define TUE   2
#define WED  3
#define THU   4
#define FRI    5
#define SAT   6
#define SUN   7

枚举示例:

enum DAY
{
     MON=1, TUE, WED, THU, FRI, SAT, SUN
};

2.类型定义和变量声明

枚举类型需要先定义后使用,这里的定义是类型的定义,不是枚举变量的定义。
既然枚举也是一种数据类型,那么它和基本数据类型一样也可以对变量进行声明。

(1)枚举类型的定义和变量声明分开

//先定义类型
enum DAY    //类型名称就是enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};

//后声明变量
enum DAY yesterday;
enum DAY today;
enum DAY tomorrow; //变量tomorrow的类型为枚举型enum DAY
enum DAY good_day, bad_day; //变量good_day和bad_day的类型均为枚举型enum DAY

(2)类型定义和变量声明同时进行

enum //跟第一个定义不同的是,此处的标号DAY省略,这是允许的。
{
    saturday,
    sunday = 0,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday
} workday; //变量workday的类型为枚举型enum DAY

(3)用typedef关键字将枚举类型定义成别名,并利用该别名进行变量声明

typedef enum workday
{
    saturday,
    sunday = 0,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday
} workday; //此处的workday为枚举型enum workday的别名
 
//或者
//enum workday中的workday可以省略
typedef enum
{
    saturday,
    sunday = 0,
    monday,
    tuesday,
    wednesday,
    thursday,
    friday
} workday; //此处的workday为枚举型enum workday的别名
 
 
//定义变量
//变量today和tomorrow的类型为枚举型workday,也即enum workday
workday today, tomorrow; 

3.枚举变量的使用

(1)先声明后赋值:

#include<stdio.h>
 
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
 
void main()
{
    /* 使用基本数据类型声明变量,然后对变量赋值 */
    int x, y, z;
    
    x = 10;
    y = 20;
    z = 30;
    
   /* 使用枚举类型声明变量,再对枚举型变量赋值 */
    enum DAY yesterday, today, tomorrow;
    
   yesterday = MON;
   today     = TUE;
   tomorrow  = WED;
 
   printf("%d %d %d \n", yesterday, today, tomorrow);
}

就像各种类型都有取值范围一样,枚举变量只能接收枚举类型中定义好的符号值(实质是一个int类型的数据)
(2)声明的同时赋值

#include<stdio.h>
 
/* 定义枚举类型 */
enum DAY { MON=1, TUE, WED, THU, FRI, SAT, SUN };
 
void main()
{
    
    /* 使用枚举类型声明变量,再对枚举型变量赋值 */
    enum DAY yesterday = MON,
             today     = TUE,
             tomorrow  = WED ;    

 
    printf("%d %d %d \n", yesterday, today, tomorrow);
}

九、typedef关键字

1.typedef与#define的区别:

(1)typedef创建的符号名只限于类型,不限于值
(2)typedef由编译器解释,不是预处理器

2.typedef的四种用法

1)typedef基本数据类型取“别名”
也就是说,C语言中的所有数据类型都可以用typedef关键词来重新定义类型名

typedef unsigned int size;
typedef unsigned int16 u16;
typedef unsigned int8 u8;

2)typedef为自定义数据类型取“别名”
自定义的数据类型包括:结构体struct name{ }; 、共用体unit name { };、枚举enum { };

 struct students
{
    char sex;
    char name[120];
    int ages;
};
typedef struct students std;
int main()
{
    std stu1;
    stu1.name[20];
    strcpy(stu1.name,"helwww");

    return 0;
}

3)typedef为数组取“别名”

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

typedef char arr_name[20];
int main()
{

     arr_name ane; 
     strcpy(ane,"helllo");
     printf("%s\n",ane);
    return 0;
}   

4)typedef为指针取“别名”

typedef int (*PF)(int,int);//函数指针别名
PF a=NULL;
typedef int (*PFS[4])(int,int);//函数指针别名
PFS b={NULL};//定义了一个函数指针数组,4个长度

例程:

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>

typedef int (*PF)(int,int);//函数指针别名
typedef int (*PFS[4])(int, int);//函数指针数组别名
int add(int a, int b) {//两个数相加
    return a + b;
}

int subtract(int a, int b) {//两个数相减
    return a - b;
}
int multiply(int a, int b) {//两个数相乘
    return a * b;
}
int divide(int a, int b) {//两个数相除
    return a / b;//会丢失精度
}
//返回函数指针
PF judge(char buff) {//判断运算符进而选择适合的函数
    switch (buff)
    {
    case '+':return add;
    case '-':return subtract;
    case '*':return multiply;
    case '/':return divide;
    default :return NULL;
        break;
    }
}

int main()
{

    char c = 0;
    int a = 0;
    int b = 0;
    int (*function)(int, int)=NULL;//函数指针
    int x;
    printf("请输入表达式:\n");
    x=scanf("%d%c%d", &a,&c,&b);
    function = judge(c);
    if (function != NULL) { //判断指针是否合法
        printf("表达式%d %c %d = %d\n", a, c, b, function(a, b));
    }else printf("表达式输入错误\n");
}

在这里插入图片描述

3.typedef中的陷阱

typedef char* PCHAR;
int strcmp(const PCHAR,const PCHAR);

在上面的代码中,“const PCHAR” 是否相当于 “const char*” 呢?

答案是否定的,原因很简单,typedef 是用来定义一种类型的新别名的,它不同于宏,不是简单的字符串替换。因此,“const PCHAR”中的 const 给予了整个指针本身常量性,也就是形成了常量指针“char* const一个指向char的常量指针)”。即它实际上相当于“char* const”,而不是“const char*指向常量 char 的指针)”。

十、 链表

1.链表概念

  • 链表特点
    (1)n个节点离散分配
    (2)每一个节点之间通过指针相连
    (3)每一个节点有一个前驱节点和一个后继节点
    (4)首节点没有前驱节点,尾节点没有后继节点
struct link{
       int data;          //定义数据域
       struct link *next; //定义指针域,存储直接后继的节点信息
};

数据域的内容可以自己指定,指针域用来存放下一个节点的地址。

  • 一些关于链表的定义
    **首节点:**存放第一个有效数据的节点

**头节点:**在单链表的第一个结点之前附设一个结点,它没有直接前驱,称之为头结点,头结点的数据域可以不存储任何信息,指针域指向第一个节点(首节点)的地址。头结点的作用是使所有链表(包括空表)的头指针非空

**头指针:**指向头节点的指针

**尾节点:**存放最后一个有效数据的节点

**尾指针:**指向尾节点的指针

  • 链表定义:
#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}

int main()
{   
    struct Test *head=NULL;
    struct Test t1={1,NULL};
    struct Test t2={2,NULL};
    struct Test t3={3,NULL};
    struct Test t4={4,NULL};
    head = &t1;
    t1.next=&t2;
    t2.next=&t3;
    t3.next=&t4;
    printlink(head);
    system("pause");
    return 0;
}

2.链表动态创建

(1)头插法

struct Test *insertFromHead(struct Test*head,struct Test *new)
{
    if(head == NULL){
        head = new;
    }else{
    	new->next=head;
		head=new;
	}
    return head;
}

用头插法动态创建链表

#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromHead(struct Test*head,struct Test *new)
{
    if(head == NULL){
        head = new;
    }else{
        new->next=head;
        head=new;
    }
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromHead(head,new);
    }
}

int main()
{   
    struct Test *head=NULL;
    head = createLink(head);
    printlink(head);
    system("pause");
    return 0;
}

(2)尾插法

struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}

用尾插法动态创建链表

#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromTail(head,new);
    }
}

int main()
{   
    struct Test *head=NULL;
    head = createLink(head);
    printlink(head);
    system("pause");
    return 0;
}

3.链表的“增删改查”

(1)增

  • 在指定数据前插入节点
int insertFro(struct Test *head,int data,struct Test *new)
{

    struct Test *p;
    p = head;
    if(p->a == data){
        new->next = head;
        head = new;
        return 1;
    }
    while(p->next!=NULL){

        if(p->next->a == data){
            new -> next = p->next;
            p->next = new;
            return 1;
        }
        p = p->next;
    }   
    return 0 ;
}
  • 在指定数据后插入节点
int insertBehind(struct Test *head,int data,struct Test * new1)
{   struct Test*p=head;
    while(p!=NULL){
        if(p->a==data){
            new1->next=p->next;
            p->next=new1;
            return 1; }       
        p=p->next;
    }
    return 0;
}
  • 示例代码
#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromTail(head,new);
    }
}
/*
    在指定数据后节点
*/
int insertBehind(struct Test *head,int data,struct Test * new1)
{   struct Test*p=head;
    while(p!=NULL){
        if(p->a==data){
            new1->next=p->next;
            p->next=new1;
            return 1; }       
        p=p->next;
    }
    return 0;
}

int insertFro(struct Test *head,int data,struct Test *new)
{

    struct Test *p;
    p = head;
    if(p->a == data){
        new->next = head;
        head = new;
        return 1;
    }
    while(p->next!=NULL){

        if(p->next->a == data){
            new -> next = p->next;
            p->next = new;
            return 1;
        }
        p = p->next;
    }   
    return 0 ;
}
int main()
{   
    struct Test *head=NULL;
    struct Test stu1 ;
    int data;
    int ret;
    head = createLink(head);
    printf("=================插入节点前=================\n");
    printlink(head);

    printf("请输入在哪个节点前插入节点:\n");
    scanf("%d",&data);
    printf("请输入需要插入的节点\n");
    scanf("%d",&(stu1.a));
    stu1.next = NULL;

    ret = insertFro(head,data,&stu1);
    if(ret){
        printf("插入节点成功!\n");
    }else{
        printf("插入失败\n");
    }
    printf("=================插入节点后=================\n");
    printlink(head);
    system("pause");
    return 0;
}

在这里插入图片描述
在这里插入图片描述

(2)删
删除指定的节点

int deleteNode(struct Test *head,int data)
{
    struct Test *p,*ptemp;
    p=head;
    if (p->a == data)
    {
        ptemp = head;
        head = head->next;
        free(ptemp);
        return 1;
    }
    while(p->next!=NULL){
        if(p->next->a == data){
            ptemp = p->next;
            p->next = p->next->next;
            free(ptemp);    
            return 1;
        }
        p = p->next;
    }

    return 0;
}
  • 示例代码:
#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromTail(head,new);
    }
}
int deleteNode(struct Test *head,int data)
{
    struct Test *p,*ptemp;
    p=head;
    if (p->a == data)
    {
        ptemp = head;
        head = head->next;
        free(ptemp);
        return 1;
    }
    while(p->next!=NULL){
        if(p->next->a == data){
            ptemp = p->next;
            p->next = p->next->next;
            free(ptemp);    
            return 1;
        }

        p = p->next;

    }

    return 0;
}
int main()
{   
    struct Test *head=NULL;
    struct Test stu1 ;
    int data;
    int ret;
    head = createLink(head);
    printf("=================插入节点前=================\n");
    printlink(head);

    printf("请输入要删除的节点:\n");
    scanf("%d",&data);

    ret = deleteNode(head,data);
    if(ret){
        printf("删除节点成功!\n");
    }else{
        printf("删除失败\n");
    }
    printf("=================删除节点后=================\n");
    printlink(head);
    system("pause");
    return 0;
}

在这里插入图片描述

(3)改

  • 修改指定节点的内容
/*
    修改指定节点
*/
int alterNode(struct Test *head,int data,int newData)
{   
    struct Test *p = head;

    while(p!=NULL){
        if(p->a==data){
            p->a = newData;
            return 1;
        }
        p=p->next;
    }
    return 0;
}
  • 示例代码
/*
    修改指定节点
*/
#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromTail(head,new);
    }
}
int alterNode(struct Test *head,int data,int newData)
{   
    struct Test *p = head;

    while(p!=NULL){
        if(p->a==data){
            p->a = newData;
            return 1;
        }
        p=p->next;
    }
    return 0;
}
int main()
{   
    struct Test *head=NULL;
    struct Test stu1 ;
    int data;
    int newData;
    int ret;
    head = createLink(head);
    printf("=================修改节点前=================\n");
    printlink(head);

    printf("请输入要修改的节点:\n");
    scanf("%d",&data);
    printf("请输入修改的内容:\n");
    scanf("%d",&newData);
    ret = alterNode(head,data,newData);
    if(ret){
        printf("修改节点成功!\n");
    }else{
        printf("修改失败\n");
    }
    printf("=================修改节点后=================\n");
    printlink(head);
    system("pause");
    return 0;
}

在这里插入图片描述

(4)查

-在链表中查找相应的节点

int searchNode(struct Test *head,int data)
{   
    while(head!=NULL){
        if(head->a==data){
            return 1;
        }
        head=head->next;
    }
    return 0;
}
  • 示例代码
#include <stdio.h>
#include <stdlib.h>
struct  Test
{
    int a;
    struct Test * next;
};
void printlink(struct Test * point)
{
    while(point!=NULL){
        printf("%d ",point->a);
        point=point->next;
        
    }
    putchar('\n');

}
struct Test *insertFromTail(struct Test*head,struct Test *new)
{   
    struct Test *p=head;
    if(p==NULL){
        head=new;
        return head;
    }
    while(p->next!=NULL){
        p=p->next;
    }
    p->next=new;
    return head;
}
struct Test *createLink(struct Test *head)
{   
    struct Test *new;
    while(1){
        new=(struct Test *)malloc(sizeof(struct Test));
        printf("输入数据(输入0结束输入)\n");
        scanf("%d",&(new->a));
        new->next = NULL; 
        if(new->a==0){
            free(new);
            return head;
        }
        head=insertFromTail(head,new);
    }
}
/*
    查找相应节点
*/
int searchNode(struct Test *head,int data)
{   
    while(head!=NULL){
        if(head->a==data){
            return 1;
        }
        head=head->next;
    }
    return 0;
}
int main()
{   
    struct Test *head=NULL;
    struct Test stu1 ;
    int data;
    int newData;
    int ret;
    head = createLink(head);
    printf("=================链表数据=================\n");
    printlink(head);
    printf("想要查找的节点\n");
    scanf("%d",&data);
    ret = searchNode(head,data);
    if(ret){
        printf("%d在链表中\n",data);
    }else{
        printf("%d不在链表中\n",data);
    }
    system("pause");
    return 0;
}

在这里插入图片描述
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值