【从0开始学习C】---2.进阶篇

前面的基础篇基本上把基础介绍的差不多了,如果写的不完善或者太晦涩可以d我一下我补一补,有一些基础概念我没写,毕竟文字太多看着容易犯困🙉

目录

补充

1.宏定义:#define

​编辑第一节、数组 

1.一维数组

2.数组的一些算法

2.1、查找算法

2.2、数组的排序

2.二维数组

2.1、数组初始化

第二节、函数

1.概念

2.有参、无参函数

3.递归


补充

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;
}

57b2991e604f4b7896776c24dd86aa34.png

如果我在第二个循环前加上#undef NUM,如下图,程序报错,表示超出了NUM的使用范围 

4a77ea0655f145c7925e5bd88b272db9.png第一节、数组 

数组算是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;
}

8c169b2fff404635a862a2437c55f6e0.png

获取数组长度: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、数组的排序

81fed598e78e4cc1ae2ea1555199b7f5.png

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;
}

代码有点绕,我自己试数了一下,过程太多放出来前两轮的参照一下。大家也可以自己拿笔写一写。

2a34be4a702d4390a0fbf09bb19cede5.png

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.求两个数的最大公约数

希望小伙伴们可以自己学习一下外部变量(外部变量)

指针单独讲一讲,本章结束。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尢词

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值