C语言(二)
6.数组
6.1数组的概念
一组具有相同类型,相同含义的数据类型的有序集合。
数组不是基本类型,是构造类型。
数组的本质/数组的存储方式:一片地址连续的空间。
6.2一维数组
语法:
元素类型 数组名[整型常量/常量表达式/变量表达式] {={初始值列表}}; 元素类型:合法类型即可 数组名 :符合C语言标识符的命名规则即可 [] :括号内指定数组的元素个数 示例: int arr[10]; int a = 10; //按照编译器支持的C标准,如果C标准在C90之前,则不可以这样定义 int arr[a]; //如果在C90之后,则可以这么定义,但这种方式不可以进行初始化
初始化:
在{ }中写入一串初始化值列表,每个初始值用逗号隔开
//示例 //对全部元素的初始化 int a[5] = {1, 2, 3, 4, 5}; //对部分元素的初始化 int a[5] = {1, 2}; //1会被赋值给a[0],2会被赋值给a[1],剩下的采用默认值0 int a[5] = {0}; //将数组中的所有元素初始化为0 //初始化值列表决定数组元素个数 int a[] = {1, 2, 3, 4, 5};//[]中的表达式被省略,这是数组的元素个数等于初始化值列表中值的个数 char str[] = "hello world";
引用数组内元素:
int a[5] = {1, 2, 3, 4, 5}; a[0];//访问数组a中的第0个元素 a[1];//访问数组a中的第1个元素 a[4];//访问数组a中的第4个元素 //访问大于元素个数的元素可能会导致程序混乱
数组的使用:
//定义一个数组,从键盘中输入各个元素的值,并依次打印出来 int a[10]; for(int i=0; i<10; i++) { scanf("%d", &a[i]); } for(int i=0; i<10; i++) { printf("%d\t", a[i]); }
//输出一个数组中所有元素的地址 int a[10]; for(int i=0; i<10; i++) { printf("%p\n", &a[i]); //%p 输出一个地址 }
练习:
1.求一个数组中所有数组元素的和,数组元素的个数自由设定
#include <stdio.h> int main() { int arr[5]; int sum = 0; for(int i=0; i<5; i++) { scanf("%d", &arr[i]); } for(int i=0; i<5; i++) { sum += arr[i]; } printf("%d\n", sum); }
2.定义一个数组(任意长度)输出一个数组中的最大值和最小值
#include <stdio.h> int main() { int arr[5]; int max,min; for (int i=0; i<5; i++) { scanf("%d", &arr[i]); } max = arr[0], min = arr[0]; for(int i=0; i<5; i++) { if(arr[i] < min) min = arr[i]; if(arr[i] > max) max = arr[i]; } printf("max:%d\nmin:%d\n", max, min); }
6.3排序问题
将一组数据按照一定的规则进行排序使之有序。
6.3.1插入排序
插入排序是一种简单直观的排序算法。它的原理是将待排序的元素依次插入到已排好序的元素序列中的适当位置,直到所有元素都插入完毕。
6.3.2冒泡排序
#include <stdio.h> int main() { int arr[10]; int temp; printf("冒泡排序,输入10个整数:\n"); for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { scanf("%d", &arr[i]); } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { for(int j = i; j < sizeof(arr) / sizeof(arr[0]); j++) { if(arr[i] < arr[j]) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } }
6.3.3选择排序
#include <stdio.h> int main() { int arr[10]; int a, b, c, index, max, temp; printf("\n选择排序,输入10个整数\n"); for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { scanf("%d", &arr[i]); } for(a = 0; a < 10; a++) { max = arr[a],index = a; for(b = a; b < 10; b++) { if (arr[b] > max) { max = arr[b]; index = b; } } temp = arr[a]; arr[a] = arr[index]; arr[index] = temp; } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } printf("\n"); }
作业:
0.实现冒泡、选择的降序排序代码
#include <stdio.h> int main() { int arr[10]; int a, b, c, index, max, temp; printf("冒泡排序,输入10个整数:\n"); for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { scanf("%d", &arr[i]); } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { for(int j = i; j < sizeof(arr) / sizeof(arr[0]); j++) { if(arr[i] < arr[j]) { temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } } } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } printf("\n选择排序,输入10个整数\n"); for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { scanf("%d", &arr[i]); } for(a = 0; a < 10; a++) { max = arr[a],index = a; for(b = a; b < 10; b++) { if (arr[b] > max) { max = arr[b]; index = b; } } temp = arr[a]; arr[a] = arr[index]; arr[index] = temp; } for(int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++) { printf("%d ", arr[i]); } printf("\n"); }
1.逆置数组中的所有元素
#include <stdio.h> int main() { int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; int temp; for (int i = 0; i < 10/2; ++i) { temp = arr[i]; arr[i] = arr[10-i-1]; arr[10-i-1] = temp; } for (int i = 0; i < 10; i++) { printf("%d ", arr[i]); } printf("\n"); }
2.假设有两个有序数组,请将这两个数组中的数据合并到第三个数组中,并且合并之后仍然有序
#include <stdio.h> int main() { int arr1[5]; int arr2[5]; int arr3[10]; int j, temp; printf("输入5个不同的整数\n"); for(int i = 0; i < 5; i++) { scanf("%d", &arr1[i]); } printf("再输入5个不同的整数\n"); for(int i = 0; i < 5; i++) { scanf("%d", &arr2[i]); } for(j = 0; j < 5; j++) { arr3[j] = arr1[j]; } for( ; j < 10; j++) { arr3[j] = arr2[j-5]; } printf("数组合并为\n"); for(int i = 0; i < 10; i++) { printf("%d ", arr3[i]); } printf("\n排序\n"); if(arr3[0] > arr3[1]) { for(int i = 0; i < 10; i++) { for(int j = i; j < 10; j++) { if(arr3[i] < arr3[j]) { temp = arr3[i]; arr3[i] = arr3[j]; arr3[j] = temp; } } } } else { for(int i = 0; i < 10; i++) { for(int j = i; j < 10; j++) { if(arr3[i] > arr3[j]) { temp = arr3[i]; arr3[i] = arr3[j]; arr3[j] = temp; } } } } for(int i = 0; i < 10; i++) { printf("%d ", arr3[i]); } printf("\n"); }
3.求最大连续子序列之和
假设有一个数组,数组元素值为:1 2 6 8 9 10 -11 3 4 5
请找出一个连续的片段,并保证这个连续的片段中的数组的和是所有可选片段中的最大值
选作:
1.完成插入排序
2.大数保存法
假设有一个很大位数的数,这个数超过了我们已知的所有基本类型可保存的最大范围,非常大,可能 long long 类型也保存不了,设计一种保存方式,可以使得这样的两个大数相加仍然可以计算结果。
6.4二分查找
假设有一组有序的序列,mid指向中间元素,left指向最左边的元素,right指向最右边的元素,相当于将这个序列一分为二;问题要求是要找到某个元素对应的下标;第一次查找和mid指向的元素进行比较,假设要找的元素比mid指向的元素大,所以mid左边的元素就不需要考虑了;因为元素还没有找到,所以需要继续二分,将left指向(mid+1)所指向的元素,mid重新指向left到right包括的新序列的中间元素,一次类推,直至找到元素。
6.5字符数组
字符数组也是一个数组,只是一般用来保存字符集/字符串
char ch[5] = {'1', '2', 'a', 'b', 'A'}; char ch1[5] = {'1', '2'} //ch1[2-4] == 0 == '\0' //输出ch中的元素可以 for(i=0; i<5; i++) printf("%c", ch[i]); //输出ch1中的元素可以 printf("%s\n", ch1);
字符集和字符串的区别
字符集:若干个字符的集合。
字符串:字符集
+ '\0'
; '\0'
是字符串的停止符
%s
:输出字符数组中的字符,直到碰到'\0'
停止
字符串的特殊表示:" "
例如"hello world"
;末尾有一个隐藏的'\0'
往
6.6二维数组
语法
数据类型 数组名[行大小][列大小]; int a[3][4]; //3行4列
初始化
int a[2][3] = {1, 2, 3, 4, 5, 6}; int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; //{1, 2, 3}赋值给第0行的第0列、第1列、第2列 //{4, 5, 6}赋值给第1行的第0列、第1列、第2列 int a[2][3] = {1, 2, 3}; int a[2][3] = {{1, 2}, {4}}; //部分初始化,其余默认给0
二维数组只能省略行大小,不能省略列大小
int a[][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; //等价于:int a[4][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
给二维数组进行赋值
int a[3][4]; for(int i = 0; i < 3; i++) //行 { for(int j = 0; j < 4; j++) //列 { scanf("%d", &a[i][j]); } }
练习:
1.定义给个二维数组,求出所有元素的最大值和最小值。
#include <stdio.h> int main() { int arr[2][5]; int min = arr[0][0], max = arr[0][0]; for(int i = 0; i < 2; i++) { printf("row %d\n", i); for(int j = 0; j < 5; j++) { scanf("%d", &arr[i][j]); } } for(int i = 0; i < 2; i++) { for(int j = 0; j < 5; j++) { if(arr[i][j] < min) min = arr[i][j]; else if(arr[i][j] > max) max = arr[i][j]; } } printf("min:%d, max:%d\n", min, max); }
2.打印杨辉三角的前10行
杨辉三角
1
1 1
1 2 1
1 3 3 1
1 4 6 4 1
1 5 10 10 5 1
#include <stdio.h> int main() { int arr[10][10]; for(int i = 0; i < 10; i++) { arr[i][0] = 1; } for(int i = 1; i < 10; i++) { for(int j = 1; j <= i; j++) { if(j == i) { arr[i][j] = 1; } else { arr[i][j] = arr[i-1][j-1] + arr[i-1][j]; } } } for(int i = 0; i < 10; i++) { for(int j = 0; j <= i; j++) { printf("%4d", arr[i][j]); } printf("\n"); } }
3.求一个二维数组的山顶元素的个数
4.现在有10级台阶,一次只能走一级或者两级,请问到十级台阶有多少种走法。
#include <stdio.h> int countWays(int n) { if (n <= 0) { return 0; } else if (n == 1) { return 1; } else if (n == 2) { return 2; } else { return countWays(n-1) + countWays(n-2); } } int main() { int n = 10; int ways = countWays(n); printf("%d\n", ways); return 0; } //也可以用一维数组实现
5.现在有一个3*4的格子,机器人从左上角的格子出发,每次都只能向下或者向上走一格,走到右下角一共有几种方法。
机器人 | 1 | 1 | 1 |
---|---|---|---|
1 | 2 | 3 | 4 |
1 | 3 | 6 | 出口==10 |
格子里的数表示走到当前格子有几种方法
#include <stdio.h> int countWays(int n, int m) { if(n == 1 || m == 1) { return 1; } else { return countWays(n-1,m) + countWays(n, m-1); } } int main() { int n = 3; int m = 4; int ways = countWays(n, m); printf("%d\n", ways); return 0; } //也可以用二维数组实现
7.函数
7.1函数
C语言中函数是指能够完成某个特定功能的代码块的封装形式。
为什么需要函数呢?
t = a; a = b; b = t; //假设这段代码需要使用很多次 //重复的代码会使程序变得臃肿 //可以将上述代码封装为一个函数 void swap(int *a, int *b) { int *t = a; *a = *b; *b = *t; } //调用这个函数 swap(&a, &b);
- 功能模块化
- 代码重复使用
- 增加代码可读性
- 方便后期的维护和升级
7.2函数的构建
语法
函数返回值类型 函数名(函数参数列表) { 函数体语句块; } //示例 int add(int a, int b) { return a + b; } //函数返回值类型:可以是任何类型。 //函数名:要符合C语言标识符规定,最好是见名知义。 //形式参数列表:简称形参 (形参类型1 形参名1, 形参类型2 形参名2, ... 形参类型n 形参名n) // 个数不固定,类型不固定,也可以没有。 //函数体语句块:函数功能的实际实现。
具体怎么写好一个函数呢
- 明确目标:函数要实现什么功能 --- 单一化,简单化
- 明确必要条件:实现这个功能,需要是什么条件(参数)
- 明确完成这个任务需要什么结果:如果不需要结果,说明不需要返回值,也就是说返回值类型为void;如果需要,说明需要返回值类型,也就是要确定返回值类型。
- 具体怎么实现:实际代码的编写。
练习:
1.写一个函数,实现求斐波那契数列的第n项
int fib(int n) { if(n == 0) return 0; else if(n == 1 || n == 2) return 1; else return fib(n-1) + fib(n-2); }
2.写一个函数,求两个数的最大公约数
int BigComDiv(int a, int b) { int min,yue = 0; if(a < b) min = a; else min = b; for(int i = 0; i <= max; i++) { if(a % i == 0 && b % i == 0) { yue = i; } } return yue; }
7.3函数调用
调用一个已经写好的函数去执行
一个函数如果不调用是不会主动执行的
语法
函数名(实参列表); or 变量名 = 函数名(实参列表);
实参和形参要一一对应,多个实参之间用逗号隔开
如果一个有返回值的函数,但是函数没有return
,那么调用这个函数会接收到一个不确定的值
函数调用过程
- 跳转 -- 跳转到被调函数入口地址
- 传参 -- 给形参分配空间,将实参的值一一对应传递给形参
- 执行 -- 执行被调函数的函数体
- 返回 --
return
7.4函数声明
告诉编译器这个函数,表示函数存在
编译器工作时,时从上往下逐行编译的
#include <stdio.h> int main() { int ret = func(); } int func() { return 0; }
此时编译器会警告ret = func()
中的func()
缺少声明
解决方法:
- 可以将被调函数放在主函数之前
- 也可以声明函数,在主函数之前进行声明
int func();
注:声明函数时,形参的变量名可以省略,但类型不能省略
7.5变量的作用域和生存周期
变量的作用域:变量生效的区域,在这个区域内可以对变量空间中的值进行读写;在这个区域外不可以对变量进行读写。
局部作用域:局部变量
某个变量的作用范围仅在与它最近的{}
内,使之生效的区域为局部作用域。形参也是局部变量,并且作用域为该函数的整个函数体内。
全局作用域:全局变量
变量在整个工程中可以被访问,这个变量成为全局变量,作用域为全局。
变量的生存周期:
变量何时分配空间,何时释放空间;变量的空间从分配到释放,这个时间段就是变量的生存周期。
生存周期的分类:
-
随代码块持续
int func() { static int a; //这个变量的生存周期随进程持续 while(1) { int i; //分配空间 ... break; //释放空间 } } 如果此时还有一个
i
的作用域大于这个i
,那么对i
的操作采取就近原则。 -
随进程持续
进程:正在进行的程序,执行可执行文件的过程,就叫做进程。
变量在程序开始带结束运行的期间,都一直保持,不被释放,直至程序退出。
例如:全局变量、被
static
修饰的变量。 -
随内核持续
只要系统没有退出,就一直存在。当然,手动申请的也可以手动释放。
练习
1.将冒泡排序封装成函数
#include <stdio.h> void maopao(int a[], int n) { int temp; for(int i = 0; i < n-1; i++) { for(int j = i+1; j < n; j++) { if(a[i] < a[j]) { temp = a[i]; a[i] = a[j]; a[j] = temp; } } } } int main() { int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } printf("\n"); maopao(a, 10); for (int i = 0; i < 10; i++) { printf("%d ", a[i]); } printf("\n"); }
2.写一个摇奖函数,随机生成行列(行最大6,列最大13),输出即可,不需要返回
#include <stdio.h> #include <stdlib.h> #include <time.h> void lottery() { srand(time(NULL)); int row = rand() % 6 + 1; int col = rand() % 13 + 1; printf("Congratulation!\nRow:%d\tCol:%d\nPlease!\n", row, col); } int main() { lottery(); }
7.6一维数组作为函数参数
int bubble(int a[], int n) { ... } int main() { int a[10]; bubble(a, 10); }
7.7宏函数和内联函数
函数内部可不可以定义函数呢?
可能可以,但没有太多的实际意义。
内联函数:由inline
关键字所修饰的函数
inline int func(int a, int b) { return a + b; } //inline 关键字:建议编译器在处理内联函数调用的时候将被调函数的函数主体拷贝到调用处 int main() { int ret = func(a, b); // <=> int ret = a + b; }
注意:inline
只是建议编译器这么做,但是编译器往往不会这么做。一般来说,内联函数要求函数体语句比较少。
- 优点:少了跳转和返回这个过程,跳转和返回是需要时间的。
- 缺点:加大了可执行文件的大小
在C\C++中用什么来实现一个短小精悍的函数:在C++中用内联,在C中用宏定义
宏函数:使用宏定义实现的类似于函数的宏
//判断两个数的大小并返回大值 #include <stdio.h> #define MAX(a, b) ((a) > (b) ? (a) : (b)) int main() { int a = 4; int b = 5; printf("%d\n", MAX(a, b)); }
宏的本质:在预处理的时候,编译器会将宏出现的所有位置替换为宏所代表的语句
作业:
1.写一个函数,求一个字符串的长度
字符串的长度:从第0个字符开始,到'\0'
为止,除去'\0'
的个数
#include <stdio.h> int StrLenth(char *p) { int len = 0; while(*p != '\0') { len++; p++; } return len; } int main() { char st[100]; int len; printf("please input string\n"); scanf("%s", st); printf("\"%s\"lenth is:", st); len = StrLenth(st); printf(" %d\n", len); }
2.写一个函数,将一串字符串中的数字字符进行求和
#include <stdio.h> int StrSum(char *p) { int sum = 0, temp = 0, state = 0; while(*p != '\0') { if(*p >= '0' && *p <= '9') { if(state == 1) { temp = temp * 10 + *p - '0'; } else { temp += *p - '0'; state = 1; } } else { state = 0; sum += temp; temp = 0; } p++; } if(state == 1) { sum += temp; } return sum; } int main() { char st[100]; int sum; printf("please input string\n"); scanf("%s", st); printf("\"%s\"sum is:", st); sum = StrSum(st); printf(" %d\n", sum); }
3.写一个函数,输出一个数的所有质因数
#include <stdio.h> void findPrimeFactor(int n) { if(n <= 1) return; for(int i = 2; i <= n; i++) { while(n % i == 0) { printf("%d ", i); n /= i; } } printf("\n"); } int main() { int n; scanf("%d", &n); printf("%d=", n); findPrimeFactor(n); return 0; }
选做:
4.写一个求根号的函数,通过二分法,找出最接近的解
#include <stdio.h> double sqrt(double n) { double min = 0, max = 100; double mid = (max + min)/2.0; if(n < 0) { return -1.0; } else {//由于精度问题,当根为无理数时,无法做到完全一致,误差小于一定范围,就可以认为相等 while((mid * mid - n) > 0.00001 || (mid * mid - n) < -0.00001) { if(mid * mid < n) { min = mid; mid = (max + min)/2.0; } else if(mid * mid > n) { max = mid; mid = (max + min)/2.0; } } return mid; } } int main() { double n,sq; scanf("%lf", &n); sq = sqrt(n); printf("%lf\n", sq); }
7.8递归
递归是一种特殊的函数调用形式,函数在函数内自己调用自己。
假如我要求一个数的阶乘
用函数体现为
int func(int n) { if(n == 1) return 1; else return n * func(n-1); }
递归分为两个过程
- 递 -- 往前走 递归公式
- 归 -- 往回走 结束条件
判断一个数是否递增,是返回1,不是返回0
int func(int *p, int n) { if(n == 1) return 1; return *(p+(n-1)) > *(p+(n-2)) && func(p, n-1); }
- 凡是用递归解决问题,技巧都是先把这个问题分解为若干个步骤,并且保证问题要么是比较简单的,要么是这个问题的子问题(与问题本身相似的问题,且规模在缩小)
快速排序:
- 首先设定一个分界值,通过该分界值将数组分成左右两部分。
- 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
- 然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
- 重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
一般不推荐使用递归
递归的优点:
- 简洁性:递归可以用更简洁的方式解决复杂的问题。
- 可读性:递归代码通常更易读和易理解。
- 模块化:递归可以将问题划分为小的子问题,提高代码的模块化程度。
递归的缺点:
- 性能损失:递归可能导致额外的函数调用开销和内存管理,可能导致性能下降。
- 栈溢出:递归的深度过大可能导致调用栈溢出,程序崩溃。
- 难以调试:递归的调试相对困难,因为每层递归都有不同的执行环境和变量状态。
- 可能造成死循环:没有正确的终止条件或终止条件设置不正确时,可能陷入无限循环。
作业
1.写一个函数,用递归实现正向输出一个整数的各位
#include <stdio.h> void list_digits(int n) { if (n < 10) { printf("%d ", n); return; } list_digits(n / 10); printf("%d ", n % 10); } int main() { int n; scanf("%d", &n); list_digits(n); }
2.求水洼数,有一个农场,地上坑坑洼洼的有很多小坑,在下雨之后,小坑里面有些有水,有些没水,有水为1,没水为0,对于连着的1,我们认为是个大水坑,我们将这个场地抽象为一个二维数组,求水坑数
#include <stdio.h> int arr[5][5] = { {0, 1, 0, 1, 1}, {1, 1, 0, 0, 1}, {0, 0, 1, 0, 1}, {1, 0, 1, 0, 1}, {0, 1, 1, 0, 0} }; void findholehole(int i, int j) { if(i >= 0 && i < 5 && j >= 0 && j < 5) { if(arr[i][j] == 1) arr[i][j] = 0; else return; findholehole(i-1, j); findholehole(i, j-1); findholehole(i+1, j); findholehole(i, j+1); } } int findhole() { int n = 0; for(int i = 0; i < 5; i++) { for(int j = 0; j < 5; j++) { if(arr[i][j] == 1) { findholehole(i, j); n++; } } } return n; } int main() { int holenum = findhole(); printf("quantity: %d\n", holenum); return 0; }
本文作者:乐情在水静气同山
本文链接:https://www.cnblogs.com/aalynsah/p/17523047.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)