数组
内存:数组存储数据的内存存储是连续的
格式:存储类型 数据类型 数组名【常量表达式】
存储类型:auto\statsic\register\const\volatile\extern
数据类型:基类型,构造类型,空类型,指针类型
数组名:满足命名规则即可
数组初始化函数:memset\bzero
memset只能对整数清0和-1
#include <string.h>//头文件
void *memset(void *s, int c, size_t n);//函数原型
//参数含义:void *s:数组名 int c:清的值 size_t n:数组的字节大小
//使用格式:
int arr[5];
memset(arr,0,sizeof(arr));
bzero:清0
#include <strings.h>//头文件
void bzero(void *s, size_t n);//函数原型
bzero(arr,sizeof(arr));//使用格式
一维数组
基础知识:
1.数组的下表从0开始,数组长度为n,则下表[0,n-1]
2.数组元素引用 arr[0]第一个元素 arr[1]第二个元素 如果n个元素,最后一个元素arr[n-1]
3.数组的越界访问 int arr[4]={11,22,33,44}; printf("%d",arr[4]);
数组地址:
1.数组名表示数组的首地址,也就是第一个元素的地址,数组名是一个常量,不可以自增或自减
2.&arr[0] = arr+0,加一就跳过一个数据类型;&arr+1表示跳过一个整个数组
数组排序方式:
数组排序有多种方法,这里列举一些常见的排序算法,它们各有特点,适用于不同的场景:
1. 冒泡排序:通过重复遍历数组,比较相邻元素并在必要时交换它们的位置,直到没有需要交换的元素为止。这是一种简单但效率较低的排序方法,平均时间复杂度为O(n^2)。
2. 选择排序:在每一轮遍历中找到数组中最小(或最大)的元素,并将其放到正确的位置上。这个过程重复进行,直到整个数组排序完成。时间复杂度同样为O(n^2)。
3. 插入排序:将数组分为已排序和未排序两部分,依次从未排序部分取出元素,插入到已排序部分的适当位置,以保持已排序部分始终有序。适合小规模数据或基本有序的数据集,时间复杂度最坏情况下为O(n^2),最好情况下为O(n)。
4. 快速排序:采用分治法策略,选择一个“基准”元素,将数组分为两部分,一部分的所有元素都比基准小,另一部分的所有元素都比基准大,然后递归地对这两部分继续进行快速排序。平均时间复杂度为O(n log n),但在最坏情况下会退化到O(n^2)。
5. 归并排序:也是一种分治算法,将数组分成两半分别排序,然后将两个有序的半部分合并成一个有序的数组。由于其稳定的性能和O(n log n)的时间复杂度,常用于大量数据的排序。
6. 堆排序:利用堆这种数据结构所设计的一种排序算法,首先将数组构造成一个最大堆(或最小堆),然后不断移除堆顶元素并调整堆的结构,最终得到一个有序的数组。时间复杂度为O(n log n)。
7. 希尔排序:是插入排序的一种更高效的版本,通过将原始数据分割成若干子序列,先使每个子序列基本有序,再对全体记录进行一次直接插入排序。它通过加大初始元素之间的间隔来改进插入排序,时间复杂度与使用的间隔序列有关,但通常优于O(n^2)。
8. 计数排序、基数排序、桶排序:这三种排序算法都是非比较排序,特别适合于整数排序,且当输入数据的范围不是很大时非常高效。计数排序基于元素的值统计每个值的出现次数;基数排序按照数字的每一位进行排序,从最低位到最高位或相反;桶排序则是将数组分布到有限数量的桶里,每个桶再分别排序。这些算法在最好情况下的时间复杂度可以达到O(n)。
注意点:
1.如果数组越界访问的内存没有被占用,则可以访问,结果是随机值
2.如果数组越界访问的内存被占用,存储不重要的数据,可以访问,结果随机值
3.如果数组越界访问的内存被占用,存储重要的数据,不可以访问,结果段错误
代码实现:
#include <stdio.h>
#include <strings.h>
int main()
{
//定义数组并初始化
int arr[5] = {0,1,2,3,4};
//定义数组并部分初始话,剩余未定义的用0填充
int arr1[5] = {0,1,2}
//省略数组长度,即[]内不填长度,编译器会自动根据元素个数来分配字节大小
int arr2[] = {0,1,2,3};//即分配16个字节
//计算数组长度和字节大小
int length = sizeof(arr2)/sizeof(int);//长度为4
int len = sizeof(arr2)/sizeof(arr2[0]);//长度为4
int size = sizeof(arr2)//大小为16
memset(arr2,0,sizeof(arr2));//arr1内元素清零
bzero(arr1,0);//arr2内元素清零
for (int i = 0; i < len; i++)
{
printf("%p\n", &arr[i])//查看数组地址
}
//相邻俩个相差4个字节
//000000722E0FF888
//000000722E0FF88C
//000000722E0FF890
//000000722E0FF894
//000000722E0FF898
//000000722E0FF89C
//000000722E0FF8A0
//000000722E0FF8A4
//000000722E0FF8A8
//000000722E0FF8AC
————————————————
return 0;
}
二维数组
与一维数组相差不大,地址也是连续的,二维数组的元素可以用一维方式打印。
int arr [ ] [ ];
#include <sdtio.h>
int main()
{
//数组定义方式
1.定义以及全部初始化
int arr[2][4]={{1,2,3,4},{5,6,7,8}}//行的全部初始化
int arr[2][4]={1,2,3,4 ,5,6,7,8};//列的全部初始化
int arr[][4]={1,2,3,4}; //当初始化时,可以省略二维数组的第一维 1行
int arr[][4]={{1,2},{3,4}};//2行
2.部分初始化,剩余元素使用0填充
int arr[2][4]={{1,2,0,0},{5,0,0,0}}//行的部分初始化
int arr[2][4]={1,2,3,0,0,0,0,0,0};//列的全部初始化
int arr[2][4]={0}
3.二维数组的单个赋值
int arr[2][3];
arr[0][0]=11;
arr[0][1]=22;
}
一维字符数组
定义:
一维字符数组通常用来存储字符串。
char myArray[10]; // 定义一个最多能存储9个字符加1个结束符'\0'的字符数组
这里,char
表示数组元素的类型是字符,myArray
是数组名,10
指定了数组的大小,即它可以存储10个字符。注意,由于字符串在C语言中是以\0
(空字符)结束的,所以实际上这个数组能存储的最大字符串长度为9个字符。
初始化:
1./*字符数组的单字符初始化*/
char str[5]={'A','B','C','D','E'}; //全部初始化
char str[5]={'A','B'}; //部分初始化,剩余元素使用'\0'=0填充
char str[]={'A','B'};//省略数组长度,默认长度是实际元素的个数
2./*字符数组的字符串初始化*/
char str[5]={"ABCD"}; //字符串的全部初始化,计算机默认添加\0
char str[5]="ABCD";
char str[5]="A"; //部分初始化,剩余元素使用'\0'=0填充
char str[]="AB";//省略数组长度,默认长度是实际元素的个数,算\0
3./*字符数组的错误初始化*/
char str[5];
str="abc";
str[5]="abc"
4./*字符数组的赋值*/
char str[5];
str[0]='a';
str[1]='b';
字符数组长度和字符串长度的理解
数组长度 字符串长度 char a[5]={'1','2','3','4','5'}; × 5 随机值 char b[5]={'1','2','3'}; √ 5 3 char c[]={'1','2'}; × 2 随机值 char d[100]="hello"; √ 100 5 char e[]="123"; √ 4 3 char f[]="123\0abc"; √ 8 3
字符数组长度sizeof:计算\0
字符串长度strlen:实际字符的个数,不计算\0
注:系统一般对相同类型的数组申请空间时,一般空间连续。
字符串函数族
使用以下函数需要用头文件<string.h>
-
strlen(const char *str): 返回字符串
str
的长度,不包括结束的空字符\0
。这是一个常用于计算字符串长度的函数。
#include <stdio.h> int main () { char str[128] = ""; printf("请输入字符串:"); scanf("%s",str); printf("1:字符串长度为%d\n",strlen(str)); int i; for (i = 0; str[i] !='\0'; i++); printf("2:字符串个数为%d\n",i); char* p = str; int count = 0; while (*p != '\0') { p++; count++; } printf("3:字符串长度为%d\n",count); }
-
strcat(char *dest, const char *src): 将字符串
src
追加到字符串dest
的末尾。要求dest
有足够的空间来容纳合并后的字符串(包括src
的长度和一个额外的\0
)。此操作会改变dest
的内容。#include <stdio.h> int main() { printf("字符串追加-----------------------------\n"); /* 初始化两个字符串常量 */ char str1[128] = "hello"; char str2[128] = "world"; /* 分别声明两个指针变量,指向这两个字符串的起始位置 */ char *p = str1; char *q = str2; /* 遍历str1,找到其结尾的空字符,为后续复制str2的字符做准备 */ while (*p != '\0') { p++; } /* 从str2的起始位置开始,逐个字符复制到str1的末尾,直到str2的结尾 */ while (*q != '\0') { *p = *q; p++; q++; } /* 在str1的末尾添加空字符,以表示字符串的结束 */ *p = '\0'; /* 打印合并后的字符串 */ printf("tr1 = %s\n", str1); }
-
strcmp(const char *s1, const char *s2): 比较两个字符串
s1
和s2
。如果s1
和s2
相等,返回0;如果s1
小于s2
(按字典序),返回负数;如果s1
大于s2
,返回值是s1-s2 ASCII。 s1,s2对应位置的字符ASCII值的大小对比,并非总字符长度。
非函数实现: int main(int argc, const char *argv[]) { char a[10]=""; char b[10]=""; gets(a); gets(b); printf("%d\n",strcmp(a,b)); //判断ab的大小关系 if(strcmp(a,b)>0 )//if(a>b) puts("a>b"); else if(strcmp(a,b)<0 )//else if(a<b) puts("a<b"); else if(strcmp(a,b)==0)//else if(a==b) puts("a==b"); //非函数实现字符串比较 char s1[100]="hello"; char s2[100]="helLo"; int i=0; while(s1[i]==s2[i]) { if(s1[i]=='\0') break; i++; } int sub=s1[i]-s2[i]; if(sub>0) printf("%s>%s\n",s1,s2); else if(sub<0) printf("%s<%s\n",s1,s2); else if(sub==0) printf("%s>%s\n",s1,s2); return 0; }
-
strcpy(char *dest, const char *src): 将字符串
src
复制到dest
中。要求dest
有足够的空间来容纳整个src
字符串(包括结束的\0
)。此操作会覆盖dest
原来的内容。
#include <stdio.h> int main() { printf("字符串拷贝----------------------------------\n"); char str1[128] = "hello_world ! 98765"; char str2[128] = "0123456789"; char *p = str1; char *q = str2; while (*p != '\0') // 要赋值的依次判断 { *q = *p; p++; q++; } *q = '\0';//将最后一个字符赋值为'\0' printf("tr2 = %s\n", str2); }
-
strncpy(char *dest, const char *src, size_t n): 将字符串
src
的前n
个字符复制到dest
中。与strcpy
不同,即使src
不足n
个字符,dest
也会被填充至n
个字符(剩余部分通常填充\0
),但不会超出n
。需要注意,如果源字符串中没有遇到\0
且复制了n
个字符,dest
可能不会被正确地以\0
结束。
-
strncat(char *dest, const char *src, size_t n): 类似于
strcat
,但是只追加src
的前n
个字符到dest
的末尾。同样,确保dest
有足够的空间。 -
strstr(const char *haystack, const char *needle): 在字符串
haystack
中搜索第一次出现字符串needle
的位置,并返回该位置的指针。如果未找到,则返回NULL。 -
strtok(char *str, const char *delim): 用于将字符串分割成一系列子字符串,基于分隔符
delim
进行切割。它会修改原字符串str
,并返回指向第一个子字符串的指针。后续调用strtok
应传递NULL作为第一个参数,以便继续分割剩余的部分。注意,strtok
是不线程安全的,因为它使用静态变量来记住上次分割的位置。 -
strcasecmp(const char *s1, const char *s2):不区分大小写的字符串比较函数,使用方法和3一致。
二维数组字符
与一维类似 ,将前面学习透彻,此节可游刃有余。
简单介绍:
格式:存储类型 char 数组名 [常量表达式1] [常量表达式2];
1.常量表达式1:第一维,表示行,表示字符串的个数
2.常量表达式2:第二维,表示列,表示每个字符串的字节大小
char a;//存储1个字符
char str [10];//存储10个字符,一个字符串
char str [3] [5];//存储5个字符串,每个字符串10个字符
一维初始化:char arr[3] = " "; 二维初始化:char arr[3][5] = {""};//大括号不能丢
char a[3][5]={"ABCD","abcd","1234"};
用的不多且简单。
函数
C语言又称为函数语言,C语言的基本单位是函数。
函数:实现特定功能的代码块
函数的优点:成程序更简洁,运行速度加快
格式
优点:成程序更简洁,运行速度加快
函数的定义格式:
格式: 存储类型 数据类型 函数名(参数列表) //函数头 { //函数体 函数体; }
1.存储类型:auto\static\extern\register\const\volatile 注意:函数省略存储类型默认是extern
2.数据类型:基类型(int float double char),构造类型(数组,结构体,共用体),空类型(void),指针类型 如果数据类型=void,无返回值函数 如果数据类型!=void,有返回值函数
3.函数名:满足命名规范即可 函数名一般驼峰命名法,下划线链接 Output output_fun
4.():函数的标志,不可以省略
5.参数列表:可有可无,如果没有则默认是无参函数,如果存在则称为有参函数,参数列表如果是多个则使用逗号隔开
6.{} 不允许省略
分类
1.库函数
库函数:系统自带的函数
IO函数:scanf\printf\getchar\putchar\gets\puts
字符函数:strlen\strcat\strcpy\strcmp\strcarcmp
数学函数:pow\sqrt
main函数:一个程序只能有一个mian函数,main函数可以调用任意函数,但是任意函数不允许调用main
2.自定函数
存储类型 函数名 (接收的参数) 函数体
viod function () {}
int...
函数内return返回的数据类型是什么,返回类型就要写什么;
注意:
自定义函数要在主函数上面,若再下面,则需要开头声明,否则报错;
全局变量和局部变量
全局变量 | 局部变量 | |
作用域 | 全局变量在程序的任何地方都是可见的,可以在文件的任何部分被访问和修改。它们的作用域是整个程序,跨越所有函数和代码块。 | 局部变量的作用域仅限于定义它们的函数、方法或代码块内部。这意味着它们只能在其定义的上下文中被访问。 |
生命周期 | 全局变量的生命周期与程序的运行周期相同。它们在程序启动时被创建,在程序结束时被销毁。 | 局部变量的生命周期与其所在的作用域相关联。当函数或代码块被执行时,局部变量被创建,执行完毕后,这些变量就会被销毁,内存被回收。 |
存储位置 | 通常存储在全局数据区或静态存储区,这使得它们在程序的整个生命周期中都存在。 | 存储在栈区,栈空间在函数调用时分配,函数返回时自动释放。 |
使用方式 | 可以在程序的任何地方直接使用,无需特别的声明或传递。 | 仅能在定义它们的函数或代码块内部使用,如果需要在其他函数中使用,需要通过参数传递或返回值等方式。 |
同名变量 | 不能重复 | 可以重复 |
默认值 | 全局变量可能会被赋予默认值,如C/C++中,整型默认为0,指针默认为NULL。 | 通常不会自动初始化,如果没有明确赋值,其内容是未定义的随机值,使用前必须手动初始化。 |
结构体
结构体(Struct)是C语言中一种复合数据类型,允许程序员将不同类型的数据组合在一起,形成一个单独的实体或记录。这种数据组织方式非常灵活,能够更加高效、直观地管理和操作复杂的数据结构。下面是结构体的一些核心概念和作用:
概念
-
定义结构体:结构体由关键字
struct
开始,后面跟着结构体的名称(可选),接着是包含在一对大括号{}
中的成员列表。每个成员可以是基本数据类型(如int
,float
,char
等)或其他结构体类型。struct ExampleStruct { int id; char name[50]; float score; };
-
声明结构体变量:定义结构体之后,需要声明结构体变量来存储具体的数据。这可以像声明其他基本类型变量一样进行。
struct ExampleStruct student1;
-
初始化结构体:结构体变量可以在声明时被初始化,或者通过赋值操作来初始化。
struct ExampleStruct student2 = {1, "Alice", 95.5};
作用
-
数据封装:结构体提供了一种方式,将相关数据项封装在一起,便于管理和操作。这对于描述现实世界中的对象(如学生信息、图书记录、员工详情等)非常有用。
-
提高代码可读性和可维护性:通过命名结构体成员,代码变得更加清晰易懂。当查看代码时,可以直接从结构体成员名了解各部分数据的意义,而不必依赖注释或上下文推断。
-
减少参数传递:在函数间传递结构体变量时,可以一次性传递一组相关数据,避免了为每个单独的数据成员分别传递参数的繁琐。
-
节省内存空间:虽然结构体成员之间可能会有对齐填充(padding),但在很多情况下,将相关数据整合在一个结构体内相比分散存储能更有效地利用内存,尤其是在处理大量同类数据时。
-
复用性和灵活性:一旦定义了结构体类型,就可以在程序的多个地方声明该类型的变量,甚至定义指向该结构体的指针或数组,从而实现代码的复用和灵活的数据操作。