前面的基础篇基本上把基础介绍的差不多了,如果写的不完善或者太晦涩可以d我一下我补一补,有一些基础概念我没写,毕竟文字太多看着容易犯困🙉
目录
补充
1.宏定义:#define
在C语言中可以用#define定义一个标识符来表示一个常量。
特点:不占内存,预编译后消失。在程序运行前由系统自动完成。预编译!=编译
优点:方便和易于维护
范围:整个程序,但是可以用#udef 标识符终止其作用域
C语言中以#开头的都是预编译(预处理)指令
语法:#define 标识符 常量 //注意,没有分号,和#include一样写在最最上面,#define定义的常量不可以二次赋值。
#include <stdio.h>
#define NUM 5
int main(void)
{
int i,j = NUM;
int a[NUM] = {0};
printf("请输入%d个数:",j);
for (i=0; i<NUM; i++) {
scanf("%d",&a[i]);
}
for (i=0; i<NUM; i++) {
printf("%d\x20",a[i]);//x20对应ASSCII表是一个空格
}
return 0;
}
如果我在第二个循环前加上#undef NUM,如下图,程序报错,表示超出了NUM的使用范围
第一节、数组
数组算是c学习之路上第一个真正意义上的存储数据的结构。数组非常重要,想必大家都知道c的指针非常复杂,数组和指针式相辅相成的,所以数组一定要好好学!!!
数组:一组由同类型的数据组成的集合,这些数据在内存中是连续存储的。
1.一维数组
定义一维数组的方式:
数组数据类型 数组名[数组长度(是一个整数)];
例如:int a[5];---a数组中有五个元素(所以长度是5),int变量在内存中占用4个字节,a中有五个元素(就是变量,数组中称为元素)就是占20个字节,并且五个元素的地址是连续的。
数组由下标取元素,下标从0开始。例如a中第一个元素=a[0],第五个元素=a[4],下标可以是数学算数,
解释:定义是无需赋值的
一维数组初始化:
1.完全初始化:int a[5] = {1,2,3,4,5};也可以写成int a[] = {1,2,3,4,5};系统会自动给数组分配长度.
2.不完全初始化:int a[5] = {1,2};//int类型默认是0,所以没有初始化的元素=0,但是只定义了数组的话,里面的元素就不是0了,而是一些垃圾值。
数组清0(就是所有元素都=0)可以写成:int a[5] = {0};
不可以写成:int a[5] = {};❌int a[];❌
初始化时大括号里的元素个数必须<=数组长度
数组的元素只能逐个引用,而不能一次引用整个数组
如果又定义了一个数组b,想把数组a赋值给b,只能用for循环,将a中的元素一个一个取出来赋给数组b。
#include <stdio.h>
int main(void)
{
int a[5] = {1,2,3,4,5};
printf("%d\n",a[1]);
printf("%d\n",a[1+2]);
printf("%d\n",a[2*2]);
return 0;
}
获取数组长度:sizeof(数组名)/sizeof(数组名[0])
2.数组的一些算法
2.1、查找算法
2.1.1、顺序查找
顾名思义就是从第一个元素开始往后查找
#include <stdio.h>
int main(void)
{
int i;
int a[10] = {1,2,3,4,5,4,3,2,2,5};
//目的:输出第一次出现的5的下标
for (i = 0; i<10; i++) {
if (a[i] == 5) {
printf("%d\n",i);
break;
}
}
return 0;
}
结果:4
2.1.2、二分查找(折半查找)
使用二分查找时数组必须是排好序的
#include <stdio.h>
int main(void)
{
int i;
int key = 55;//要查找的目标
int low = 0;//初始最小下标
int high = 9;//初始最大下标
int mid;//定义中间下标
int a[10] = {1,2,3,4,5,6,44,55,77,121};
//目的:查找55
mid = (low+high)/2;
while (low<=high) {
if (mid>key) {//如果目标比中间量小,则它在中间量的左边,那最大下标就变成了中间下标
high = mid;
}else{
low = mid;
}
mid = (low+high)/2;//更新中间下标
if (a[mid] == key) {
printf("%d",mid);
}
}
return 0;
}
结果:7
数组的插入和删除都需要定义一个新的数组,因为插入和删除改变了数组的长度。
2.2、数组的排序
1.冒泡排序
以小到大排,冒泡排序就是相邻的两个数进行比较,小的放左边,大的放右边
本质是每一轮都把最大的移到最右边
#include <stdio.h>
int main(void)
{
int i,j;
int a[] = {90,21,132,-58,34};
for (i = 0; i<5-1; i++) {//五个数排序只需要排4轮
for (j=0; j<5-1-i; j++) {//每排一轮下一轮就少排一个数
if (a[j]>a[j+1]) {
//换位置
a[j] += a[j+1];
a[j+1] = a[j]-a[j+1];
a[j]-=a[j+1];
}
}
}
for (i=0; i<5; i++) {
printf("%d\t",a[i]);
}
return 0;
}
结果:-58 21 34 90 132
2.插入排序
把第二个及之后的元素单拎出来与前面的一个一个比较
#include <stdio.h>
int main(void)
{
int i,j,temp;
int a[] = {90,21,132,-58,34};
for (i = 1; i<5; i++) {
j = i-1;
temp = a[i];
while (j>=0&&a[j]>temp) {
//交换位置
a[j+1] = a[j];
--j;
}
if (j!=i-1) {
a[j+1] = temp;
}
}
for (i = 0; i<5; i++) {
printf("%d\t",a[i]);
}
return 0;
}
代码有点绕,我自己试数了一下,过程太多放出来前两轮的参照一下。大家也可以自己拿笔写一写。
3.选择排序
和冒泡恰恰相反,将最值放在最左边。但是冒泡排序是每比较一次就互换一次,而选择排序是一轮比较完选出最值放在左边。也就是一轮下来只换一次位置。
#include <stdio.h>
int main(void){
int i,j,MinIndex;
int temp;
int a[] = {90,21,123,-58,34};
int n = sizeof(a)/sizeof(a[0]);//记录数组长度
for(j = 0;j < n;j++){
MinIndex = j;
for(i = j+1; i<n;i++){
if(a[MinIndex] > a[i]){
MinIndex = i;
}
}
if(MinIndex!=j){
temp = a[j];
a[j] = a[MinIndex];
a[MinIndex] = temp;
}
}
// 输出最终排序结果
for(i = 0;i<n;i++){
printf("%d\t",a[i]);
}
return 0;
}
下面我试数了第一轮(字丑🤦♀️)
最终结果:-58,21,34,90,123
4.快速排序
这个算法尤为重要,必须掌握。所涉内容有递归函数调用,指针,没有基础的小伙伴可以先跳过,后面再回来看。
数组是不支持随时插入的,所以使用的是“舞动算法”,先上代码,等看过后面的函数,递归,指针后再来理解吧。
#include <stdio.h>
void Swap(int *,int *); // 函数声明 用来交换两个变量
void FastSoft(int *,int,int); // 声明用来快速排序的函数
int main(void){
int i;//定义循环变量
int a[5] = {90,21,123,-58,34};
FastSoft(a,0,4);//a-->数组名 0-->第一个元素的下标,4-->最后一个元素的下标
printf("最终结果:\n");
for(i = 0;i < 5;i++){
printf("%d\t",a[i]);
}
return 0;
}
void Swap(int *p,int *q){
int temp;
temp = *p;
*p = *q;
*q = temp;
return;
}
void FastSoft(int *a,int low,int high){
int i = low;
int j = high;
int key = a[low];
if(low >= high){//说明排序结束了
return;
}
while(low < high){//本层循环控制轮数
while(low < high && key <= a[high]){
--high;//向前寻找
}
if(key > a[high]){
Swap(&a[low],&a[high]);
++low;
}
while(low < high && key >= a[low]){
++low;
}
if(key < a[low]){
Swap(&a[low],&a[high]);
--high;
}
}
FastSoft(a,i,low-1);
FastSoft(a,low+1,j);
}
2.二维数组
其实二维数组就是excel表,有行有列,每一行都是一个一维数组
一般形式:
数据类型 数组名[常量表达式][常量表达式];
例如:int arrs[3][4];=====>三行四列的数组,元素的名字如下:
2.1、数组初始化
完全初始化:int arrs[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
也可以写成:int arrs[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};====>不建议
完全初始化的时候第一个中括号里可以不写长度,但是第二个必须写
不完全初始化:int arrs[3][4] = {{1,2},{5},{9}};===>和一维数组一样其它未初始化的值是0
二维数组清零:int a[3][4] = {0};
二维数组的输出需要使用嵌套for循环,下面那看例子:
#include <stdio.h>
int main(void) {
int arrs[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int i = 0;i < 3;i++)//控制行
{
for (int j = 0; j < 4; j++)//控制列
{
printf("%d\t", arrs[i][j]);
}
printf("\n");
}
return 0;
}
写一个小练习吧~
杨辉三角
#include <stdio.h>
#define N 10
int main(void) {
/*
* 杨辉三角:
* 1
* 1 1
* 1 3 1
* 1 3 3 1
* 1 4 6 4 1
* ......
* 分析:
* 1.j==0和i==j的时候,都是1
* 2.arr[i][j] = arr[i-1][j]+arr[i-1][j-1],i!=j
*/
int i, j;
int a[N][N] = { 0 };
for ( i = 0; i < N; i++)
{
for ( j = 0; j <= i; j++)
{
if (i==j or j==0)
{
a[i][j] = 1;
continue;
}
a[i][j] = a[i - 1][j] + a[i - 1][j - 1];
}
}
printf("杨辉三角:\n");
for ( i = 0; i < N; i++)
{
for ( j = 0; j <= i; j++)
{
printf("%-3d\x20", a[i][j]);//-表示左对齐,不写-就是右对齐,3表示3个空格,\x20也是空格
}printf("\n");
}
return 0;
}
改变输出方式
#include <stdio.h>
#define N 10
int main(void) {
/*
* 杨辉三角:
* 1
* 1 1
* 1 3 1
* 1 3 3 1
* 1 4 6 4 1
* ......
* 分析:
* 1.j==0和i==j的时候,都是1
* 2.arr[i][j] = arr[i-1][j]+arr[i-1][j-1],i!=j
*/
int i, j;
int a[N][N] = { 0 };
for ( i = 0; i < N; i++)
{
for ( j = 0; j <= i; j++)
{
if (i==j or j==0)
{
a[i][j] = 1;
}
else {
a[i][j] = a[i - 1][j] + a[i - 1][j - 1];
}
printf("%d\t", a[i][j]);
}
printf("\n");
}
return 0;
}
不存在多维数组
第二节、函数
c语言有两个必学的知识点---函数和指针。这两个是c语言的主体和核心,重要性可想而知。
c语言的函数有一个特点,就是它有固定的格式和固定的模型。
1.概念
什么是函数?
函数就是c语言的模块,一块一块的,有较强的独立性,但是可以相互调用。c和c++的区别就是c++对象可以独立完成功能,无需调用。
c语言的组成和编译单位
一个c程序是由一个或多个程序模块组成的,每个程序模块即为一个源程序(.c文件)。一个.c文件可以被多个c程序共用。一个.c文件是一个编译单位。
库函数和自定义函数
库函数是c提供给我们使用的,提供一些基本功能
自定义函数是我们自己编写的可以实现某一个特定功能的程序
函数的调用
函数与函数之间是平行关系,就是函数不能嵌套定义。mian函数可以调用其它函数但不能被其它函数调用。一个c源程序有且仅有一个主函数。
2.有参、无参函数
从形式上看,函数分为两类:有参函数和无参函数。所谓无参函数就是掉用函数时不需要给它传递数据。
有参函数就是调用的时候需要传递数据⭐
有参函数的一般形式为:
函数(返回值)类型 函数名(参数1,参数2,...){
声明部分
语句部分
}
举个例子🌰🌰
#include <stdio.h>
int main(void) {
//两求最大值
int Max(int x,int y);//函数声明,x和y是形参
int a = 1, b = 9;
printf("the max is: %d\n", Max(a, b));
return 0;
}
//函数定义
int Max(int a, int b) {
if (a>b)
{
return a;
}
return b;
}
形参就是x,y这样没有固定值的变量,实参就是a,b这样确定值的常量。
参数传递的时候为“值传递”,讲究实参与形参:数量一致,按顺序一对一,数据类型一致或兼容
函数声明和函数定义是不一样的,函数声明是一条语句,有分号,函数定义没有分号。
函数在调用之前一定要存在,可以先声明不定义,也可以直接定义不声明。函数的声明一定是在主函数的开头或之上。以上的函数声明也可以写成int Max(int,int);因为系统值检查参数个数和类型,并不检查参数名。一般将函数的声明写在#include下面,int main上面。
定义函数时要指定返回值类型,无返回值就写void,不写系统默认为int(可读性差,别这么干)
函数的返回值是return 值;语句返回的。没有返回值可以写return;跳出函数。
函数返回值类型不是void时,必须写return语句,return(返回值)<===>return 返回值。
写一个小作业吧,定义一个函数判断一个自然数是否是素数吧
#include <stdio.h>
#include <corecrt_math.h>
int Prime(int x);
int main() {
int m = 15;
if (0==Prime(m))
{
printf("不是素数");
}
else
{
printf("是素数");
}
return 0;
}
int Prime(int x) {
int i;
if (1 == x)
{
return 0;
}
else if (2 == x)
{
return 1;
}
else if (0 == x % 2)
{
return 0;
}
else
{
for (i = 3; i <= sqrt(x); i++)//sqrt是根号
{
if (0 == x % i)
{
return 0;
}
}
}
return 1;
}
3.递归
自己调用自己就是递归。必须满足两个条件:
1.要有递归公式
2.要有终止条件
不要去考虑一个问题能不能用递归解决,我们所要做的就是掌握那些已知的、非常经典的递归算法。当且仅当一个算法存在预期的收敛效果(终止条件)时,采用递归算法才是可行的。
理论上讲:
所有循环都可以写成递归,但是不是所有的递归都能写成循环。
递归的优点:简化程序设计,结构简介清晰,容易编程,可读性强,容易理解
递归的缺点:速度慢,运行效率低,对存储空间的占用比循环多。
举例学习🌰🌰🌰
要想弄明白递归的运行原理,先学习关于栈的知识吧
#include <stdio.h>
long Factorial(int n);
//递归求n的阶乘
int main() {
//调用函数
printf("%ld\n", Factorial(4));
return 0;
}
//递归
long Factorial(int n) {
if (n < 0) {
return -1;
}
else if (1 == n or 0 == n)
{
return 1;
}
else {
return n * Factorial(n - 1);
}
}
斐波那契数列是一个典型的用递归算法实现的程序👇
斐波那契数列:0,1,1,2,3,5,8,13.........
#include <stdio.h>
long fbnq(int n);
//求斐波那契的第n项
int main() {
//调用函数
printf("%ld\n", fbnq(4));
return 0;
}
//递归
long fbnq(int n) {
if (n < 0) {
return -1;
}
else if (1 == n)
{
return 0;
}
else if (2 == n)
{
return 1;
}
else {
return fbnq(n - 1) + fbnq(n - 2);
}
}
写个小作业吧
1.利用递归计算1加到100的和
2.求两个数的最大公约数
希望小伙伴们可以自己学习一下外部变量(外部变量)
指针单独讲一讲,本章结束。