数组

定义

  • 命名相同类型的有限变量的集合,该名称是一个数组名称。

  • 组成数组的变量称为数组的元素。

声明和访问

声明

定义一个数组时所需的基本语法

int scores[5]

初始化

int scores[5] = {90, 85, 80, 75, 70};

初始化部分元素

int scores[5] = {90, 85};

初始化第一,第二元素,其余为0

让编译器自动推断大小

int scores[] = {90, 85, 80, 75, 70};

使用索引访问元素

printf(“%d”, scores[0]); // 输出90

pfor(int i=0;i<=n;i++)
printf(“%d”, scores[i]); // 输出90

注意点:数组的索引从0开始,超出数组范围的索引可能导致未定义的行为

用取模来实现下标循环

多维数组

多维数组就像是个表格,矩阵,有行和列

初始化和声明

声明一个3x4的二维数组
int a [3] [4];

初始化
int a[3] [4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};

访问元素
printf(“%d”, a[1][2]); // 输出7

使用双重循环遍历
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 4; j++){

printf(“%d “, a[i][j]);

}

printf(“\n”);

}

还可以用取模的方法只用一个循环遍历

字符数组

定义

字符数组可以用来存储一个字符序列。例如:char name[5];

字符串与字符数组

在C中,字符串其实是一个字符数组,其中每个字符都按顺序存储,而且最后有一个额外的字符‘\0’,表示字符串的结束。这就是为什么字符串”John”实际上在内存中占据5个字符的空间:’J’, ‘o’, ‘h’, ‘n’, 和 ‘\0’。

初始化

1
2
char name[] = "John"; 
char name[5] = "John";

元素为 ‘J’ ‘o’ ‘h’ ‘n’ ‘\0’

访问和修改字符数组

printf(“%c\n”, name[2]); // 输出h

pprintf(“%s\n”, name);// 输出 John

name[2] = ‘a’; //其余元素不变

字符串函数

头文件<string.h>

strlen(): 返回字符串的长度(不包括结束字符\0)。

strcpy将一个字符串拷贝给另一个字符串
返回值会返回被传的字符串

strcat(): 连接两个字符串。

strcmp(): 比较两个字符串。

基本形式为strcmp(str1,str2),若str1=str2,则返回零;若str1<str2,则返回负数;若str1>str2,则返回正数,返回值只与第一个不同字符有关,与第二个不同字符无关

在字符串中找一个字符strchr(左到右),strrchr(从右到左)

返回值为指针,即第一次出现的位置,返回null为没找到

若想找第二个字符,以下为一种方法(左到右):

(p为指针)

p=strchr(str,’a’);
p=strchr(p+1,’a’);

strstr字符串内找字符串
strcasestr找的过程内忽略大小写

函数

定义

函数是C语言中的一个基本构建块,用于封装代码以执行特定的任务

函数是为执行特定任务而组织的一段程序代码,它可以有输入(称为参数)和输出(返回值)

函数就是把实现某一个功能的所有的代码打成一个包,每次需要这个功能的时候不用重复去写实现这个功能的代码,而是使用函数

声明与访问

声明:函数声明告诉编译器函数的名称、返回类型和参数。

int add(int a,int b);

定义:函数的定义提供函数的实际实现
int add(int a, int b) {
return a + b;
}

调用
int sum = add(5, 6);

函数的参数
按值传递:这是C语言的默认参数传递方式,函数接收参数的值,对参数的修改不会影响调用者。

按引用传递:C语言通过传递指针来实现这一点。函数接收参数地址的指针,因此可以修改原始数据。

返回值

函数可以返回一个值给调用栈,这可以通过’return’语句实现

形参与实参

形参
int add(int a, int b) {
return a + b;
}

实参
int sum = add(5, 6);

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
void swap(int x, int y) {
int tmp = x;
x = y;
y = tmp;
}
int main() {
int a = 4, b = 51, c = 14, d = 1;
swap (a, d);
swap (c, b);
swap (b, c);
swap (c, d);
swap (b, d);
printf("%d %d %d %d", a, b, c, d);
return 0;
}

该函数无返回值,形参改变不影响实参,所以a,b,c,d还是原值

数组做参数

数组做参数时,传达的是一个地址

可在函数内通过对地址上的形参进行修改来修改实参

递归

原理

在一个函数中反复调用它自身

汉诺塔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
void hanoi(int n, char from, char to, char aux) {
if (n == 1) {
printf(" %c -> %c\n", from, to);
return;
}
hanoi(n - 1, from, aux, to);
printf("%c -> %c\n", n, from, to);
hanoi(n - 1, aux, to, from);
}
int main() {
int n = 4; // Number of disks
hanoi(n, 'A', 'C', 'B'); // A, B and C are names of rods
return 0;
}

思路

当n个盘的最大的盘在底部时,可将其看成n-1的盘进行移动

三个柱子,分别为 出发地,中转地,目的地

每次最大盘到达目的地时,随后看成n-1盘

出发地,中转地,目的地改变

如此递归

非尾递归与尾递归

非尾递归

非尾递归意味着递归调用不是函数的最后一个操作,递归调用的结果通常需要进一步处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <time.h>
int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
return fib(n - 1) + fib(n - 2);
}

int main() {
double duration;
clock_t start, stop;
int result;
start = clock();
result = fib(40);
stop = clock(); //结束计时
duration = ((double)(stop - start))/CLOCKS_PER_SEC;
printf("%d\n", result);
printf("%f",duration);
return 0;
}

尾递归

尾递归意味着递归调用是函数的最后一个操作。编译器能够优化尾递归,将其转换为迭代调用,这样可以避免栈溢出的问题并提高效率。

尾递归效率比非尾递归快的原因

非尾递归过程中对一相同过程进行了多次反复运行