达内C语言学习笔记(day07)

day07:二维数组和函数

每日英语

row:行

col:列

extern :外部的

void:空的,没有的意思

function:函数,功能

ret = return:用来保存返回值

overflow:溢出

sawp:交换

implicit declaration of function ‘exit’:exit函数没有声明,问题原因是要不函数名写错了,要么就是没有添加对应 头文件

clear:清0

set:置1

回顾:

1. 循环结构

for循环

while循环

do…while循环

2. goto语句

标签1:
	语句
goto 标签1/标签2
标签2:
	语句;

掌握goto语句经典的使用编程模板

3. 空语句

空死循环:for(;😉;

空有效循环:延时作用,延时时间不精确

可悲事情:严防将有效循环变空循环

4. 一维数组

简化定义大量同数据类型变量

定义数组(利用数组分配内存)语法:元素数据类型 数组名[元素个数/长度] = {初始值(,)};

例如:int a[5] = {1, 2, 3, 4, 5};

立马浮现内存分布图!

定义数组的七种方式

数组元素访问:数组名[下标] ,a[3],越界访问

数组公式:

​ 数组名是数组的首地址,并且等于第0个元素的首地址

​ sizeof(数组名) 会得到数组占用的内存总大小

​ sizeof(数组名[0])会得到数组每个元素占用的内存大小

​ 数组长度/数组长度 = sizeof(数组名)/sizeof(数组名[0])

注意:

​ 严防写垃圾代码

5. scanf函数

从键盘获取数值并且保存到变量中

int a, b;

scanf("%d%d", &a, &b); // 输入数字时,用空格键分开

6. 变长数组(了解)

int len;
scanf("%d,", &len);
int a[len];
for(i = 0; i < 5; i++) {
	a[i] = i + 1;
}

7. 二维数组

7.1 二维数组的定义

二维数组中的每个元素是一个一维数组,而一维数组又包含若干个数据

所以二维数组由一维数组组成,本质来说二维数组就是一维数组

就是讲一维数组进行了再分组而已

7.2 定义二维数组语法格式

又称用二维数组分配内存的方法

元素数据类型 二维数组名[二维数组长度] [一维数组长度] = {初始化值}

例如:int a[5] [3] = {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15}; 俗称5行3列

语义:

  1. a代表二维数组,包含5个元素,每个元素是一个一维数组

    而一维数组中又包含3个元素(数字)

    所以a二维数组名就是二维数组的首地址

  2. a[0]代表二维数组a的第0个一维数组,a[0]就是第0个一维数组的首地址

    它同样也等于a二维数组的首地址,即:a = a[0]

    a[1]代表二维数组a的第1个一维数组,a[1]就是第1个一维数组的首地址

    ……

  3. a[0] [0]代表二维数组a的第0个一维数组中的第0个元素(数字)

    a[1] [2]代表二维数组a的第1个一维数组中的第2个元素(数组)

    例如:a[0] [0] = 1

    ​ a[1] [2] = 6

    **通用形式:**a[i] [j] 代表二维数组a的第i个一维数组的第j个元素值

    ​ 将来可以通过此形式,来访问二维数组中的每个形式

    例如:

    printf("%d\n", a[0][0]);  // 打印1
    a[1][2] = 6
    
  1. 二维数组a分配内存总大小等于15个元素的内存,而每个元素右占4个字节,所以总共15*4 = 60个字节

  2. 二维数组中每个元素数字的访问需要用两层for循环

    for(...) {  // 控制行,一维数组
    
    	for(...) {  	//控制列,数字
    
    }
    
    }
    
参考代码:array.c
/*二维数组演示*/
#include <stdio.h>

int main(void) {
	//定义并初始化二位数组
	int a[5][3] = {{1,2,3},{4,5,6},{7,8,9},{10,11,12},{13,14,15}};
	
	//正向打印二位数组每个数字
	for(int i = 0; i < 5; i++) {  // 打印有多少个一维数组
		for(int j = 0; j < 3; j++) {  // 控制一行有多少数字
			printf("%d", a[i][j]);
	}
	printf("\n");
}
	
	printf("以下是扩大10倍后打印:\n");	

	// 将二维数组中每个数字扩大10倍
	for(int i = 0; i < 5; i++) {  // 打印有多少个一维数组
		for(int j = 0; j < 3; j++) {  // 控制一行有多少数字
			a[i][j] *= 10;
	}
}
	//正向打印二维数组
	for(int i = 0; i < 5; i++) {  // 打印有多少个一维数组
		for(int j = 0; j < 3; j++) {  // 控制一行有多少数字
			printf("%d ", a[i][j]);
	}
	printf("\n");
}	

	return 0;
}

7.3 定义二维数组的七种形式

形式1:

int[5][3];   //只定义不初始化

形式2:

int a[5][3] =  {{1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15};   //标准形式

形式3:

int a[5][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,15};

形式4:

int a[5][3] = {0};

形式5:

int a[5][3] = {{1, 2}, {0}};

形式6:

int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8};   // gcc自动分2组,等价于a[2][4]

错误形式7:

int a[2][3] = {{1,2},{3,4},{5,6}}; // 本来2行3列,错误搞成了3行2列,越界访问

**注意:**二维数组一旦初始化,二维数组的长度是可以省略的,但是一维数组的长度不可省略(gcc迷茫了)

例如:

int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12};
printf("%d\n", a[2][3]);   // 12
printf("%d\n", a[0][3]);  // 4
printf("%d\n", a[1][2]);   //7
a[2][2] = 250;11修改为250

7.4 二维数组终极公式

例如:

int a[2][3] = {0};
1. a是二维数组的首地址 = 第0个一维数组的首地址a[0] = 第0个一维数组的第0个元素的首地址&a[0][0]
2. sizeof(a): 获取整个二维数组占用的内存总的大小 = 2*3*4 = 24字节
3. sizeof(a[0]): 获取二维数组的第0个一维数组占用的内存总大小 = 3*4 = 12字节
4. sizeof(a[0][0]): 获取到二维数组的第0个以为数组中的第0个一维数组占用的内存大小 = 4
5. sizeof(a) / sizeof(a[0]) = 二维数组的长度 = 24 / 12 = 2行
6. sizeof(a[0]) / sizeof(a[0][0]) = 一维数组的长度  = 12 / 4 = 3列
7. sizeeof(a) / sizeof(a[0][0]) = 所有元素的个数 = 24 / 4 = 6个元素(数字)
参考代码:array2.c

第七课:函数(核心中的核心)

1.明确

任何C程序的源文件都包含两部分内容:一堆变量(包括数组)和一堆函数构成

2.函数概念

函数就是一堆执行语句的组合,用于实现一些相对独立并且具有一定通用性的功能

函数的特性:独立性和通用性

3.为何掌握函数这个技术

例如:

要求完成:实现两个正数相加

张三同学的代码:

/*add.c*/
#include <stdio.h>
int main(void) {
	int a, b, sum = 0;
	scanf("%d%d", &a, &b);
	if(a < 0 || b < 0) {
	printf("请重新输入两个正数:\n");
	return -1;
	}
	// 如果是正数,做加法运算
	sum = a + b;
	printf("sum = %d\n", sum);
	return 0;
}
李四同学的代码:

/*add2.c*/
#include <stdio.h>
int main(void) {
	int a = 2, b = 3, sum = 0;
	if(a < 0 || b < 0) {
	printf("请重新输入两个正数:\n");
	return -1;
	}
	// 如果是正数,做加法运算
	sum = a + b;
	printf("sum = %d\n", sum);
	return 0;
}

其余学生:add3.c …add35.c

结论:如果要完成这个功能,所有的人,都需要将很多重复的代码写很多遍,加大了开发的工作量,代码极其啰嗦

期望:只需将重复性的代码写一遍即可,其他人的代码直接拿去用即可,降低开发的工作量

问:如何实现呢?

答:采用函数这个技术

终极解决方案:

vim add.c
// 编写一个add加法函数
int add(int x, int y) {   // x = a, y = b
	if(a < 0 || b < 0) {
	printf("请重新输入两个正数:\n");
	return -1;  // 函数结束后面的代码不执行
	}
	// 如果是正数,做加法运算
	sum = a + b;
	printf("sum = %d\n", sum);
    return sum;	  // 把计算结果进行返回
}
张三同学的代码:

vim main1.c

int main(void) {
	int a, b, sum = 0;
	scanf("%d%d", &a, &b);
	// 调用add函数,清切把a,b这两个值传递给add函数
	//并且add函数执行的求和结果返回给main函数sum变量
	sum = add(a, b);
	return 0;
}

结论:如果将来任何人想实现两个正数相加,无需编写加法代码,只需调用编写好的且具有独立性和通用性的add函数即可,减少了开发的工作量

4.函数使用三步骤(三剑客)

4.1 函数声明

  1. 函数声明的功能:告诉gcc编译器,将来这个函数可以给别人或者自己使用

    函数声明是不会分配内存空间的

  2. 函数声明的语法:extern 返回值数据类型 函数名(形参表);

    例如:extern int add(int x, int y);

    注意:

    1. extern关键字可以省略,由衷建议加上去(目的:提高代码可读性)

    2. 如果函数定义在函数调用之前,可以省略编写函数声明,否则必须

      在函数调用的前面编写函数声明代码

    3. 函数名要符合标识符的命名规则

    4. 如果函数执行完毕,需要返回数字,这个数字的数据类型就是返回值的数据类型

      如果函数执行完毕没有返回值,不需要返回数值,返回值数据类型写void关键字

      例如:extern void add(int x, int y);

    5. 形参表就是定义了一堆变量而已,例如:int x, int y

      用于保存接收调用函数时传递的参数,例如:add(a, b)

      也就是变量a的值将来会赋值给变量x变量、

      也就是变量b的值将来会赋值给y变量

      形参表的变量名在函数声明时可以不用写:例如:extern int add(int, int);

      如果函数不需要传递参数,形参表直接写void,例如:extern int add(void);

    6. 形参表变量的个数要小于等于4个,提高函数调用的代码执行效率

      例如:

      extern int add(int x, int y, int m, int n);  //此函数调用效率高
      extern int add(int x, int y, int m, int n, int z); // 效率低
      

4.2 函数定义

  1. 函数定义概念:就是一个函数的具体实现过程,编码过程,函数的定义又称函数封装或者函数实现

    函数定义是会分配内存的

    一旦实现完毕,将来别人或者是自己的代码就可以访问调用了

  2. 函数定义的语法格式:

    返回值数据类型	函数名(形参表){
    	一堆得函数体语句;
    }
    
    例如:
    int add(int x, int y)
    {
    	return x + y;
    }
    
  3. 函数定义的注意事项

    1. 函数定义的返回值数据类型,形参表要和函数声明时的返回值的数据类型,形参表保持一致

      否则编译器gcc报错

    2. 函数执行完毕如果要返回一个数字,这个数字的数据类型,就是返回值的数据类型

      如果函数执行完毕没有或者不需要返回值数值,返回值数据类型写void关键字

    3. 形参表就是一堆变量的定义,个数小于等于4个,保存调用函数残敌的参数数值

      如果没有形参,也就是不需要传递参数写void

    4. 函数执行完毕如果要返回一个数字,用return关键字,语法:return 常量数字或者变量;

      例如:

      int add(int x, int y) {
      	return 100;
      	或者
      	return x + y;
      }
      

      函数执行完毕没有返回值,可以不用return 也可以用return,此时return后面什么都不跟

      例如:

      void add(int x, int y) {
      	...
      	return;   // 也可以不加
      }
      

      注意:只要函数鹏到return关键字,此函数立马返回,函数里面后续代码不再执行

4.3 函数调用

  1. 函数调用的概念:俗称使用函数,调用函数,访问函数

  2. 函数调用语法:接收函数返回值的变量 = 函数名(实参表);

    例如:

    int main(void) {
    	int a = 10;
    	int b = 20;
    	int sum = 0;
    	sum = add(a, b);  // 调用add函数并且给ad函数传递参数并且用sum,保存add函数的返回值
    	
    	return 0;
    }
    

    注意:实参表即可是常量也可是变量

    ​ 例如:

    add(100, 200);
    add(a, b);
    

    gcc编译器将来自动将实参的值会拷贝一份赋值给形参变量

    切记:实参变量和形参变量的内存是独立的,各自是各自的,只是里面储存值是一样的而已

    例如:

    int add(int x, int y);  // 结果; x = a =100, y = b = 200
    

5. 函数使用的四种形式

形式1:无形参无返回值

void 函数名(void){
	函数语句;
}
参考代码:function.c
int main(void) {
	/*调用print函数*/
	printf("我现在在print函数里面,马上调用print函数\n");
	print();
	print1();   // 调用print1函数
	printf("调用完毕print,print1,再返回到main函数继续运行\n");
	return 0;
}

/*第二步:print函数定义*/

void print(void) {
	printf("我在print函数里面做的打印.\n");
	//return;   可以用也可以不用
} // 如果没有return,程序执行到这个花括号,函数就返回


结果:
我现在在print函数里面,马上调用print函数
我在print函数里面做的打印.
我在print1函数里面做的打印
调用完毕print,print1,再返回到main函数继续运行

形式2:无形参有返回值

返回值数据类型A		函数名(void) {
	函数语句;
	return 返回值B;
}
注意:
1. 返回值B的数据类型要和返回值数据类型A要保持一致,否则gcc编译器会将返回值B的数据类型意识转换为A的数据类型
2. 如果函数返回忘记写return 返回值B;
	gcc编译器会自动给你返回一个乱七八糟的数
3. 返回值B可以是常量也可以是变量
4. 如果没有实参,圆括号里面什么都不写,例如: add();
参考代码:function1.c
/*无形参有返回值*/
#include <stdio.h>

/*第一步声明read1和read2函数*/
extern int read1(void);
extern int read2(void);
extern int read3(void);
extern char read4(void);

int main(void) {
       //调用read1和read2函数
	int ret = 0; // 此变亮调用read1和read2的返回值
	ret = read1();
	printf("read1函数的返回值是%d\n", ret);
	ret = read2();
	printf("read2函数的返回值是%d\n", ret);
	ret = read3();
	printf("read3函数的返回值是%d\n", ret);
	ret = read4();
	printf("read4函数的返回值是%d\n", ret);
	return 0;
}


/*第二步:定义read1,read2,read3,read4函数*/
char read4(void) {  // char:-128-127 : 300 - 256 = 44
	return 300;  //300默认类型为int类型,而返回值要求是char,编译器将int类型转换位char类型进行返回
}

int read1(void) {
	printf("我在read1函数里做的打印\n");
	return 250;	// 返回常量
}


int read2(void) {
	printf("我在read2函数里做的打印\n");
	int a = 520;
	a++;
	return a;   // 返回变量的值
}

int read3(void) {
	printf("我在read3里做打印\n");
}  // gcc返回一个乱七八糟的随机数

结果:
我在read1函数里做的打印
read1函数的返回值是250
我在read2函数里做的打印
read2函数的返回值是521
我在read3里做打印
read3函数的返回值是24
read4函数的返回值是44

形式3:有形参无返回值

void 函数名(形参变量的个数建议小于等于四个) {
	函数语句;
	return; // 可以加也可以不加
}

注意:形参变量的内存和实参变量的内存是独立的,各自是各自的,保存的值是一样的,因为在传递参数时做了拷贝赋值而已

在这里插入图片描述

参考代码:function2.c
/*有形参无返回值演示*/
#include <stdio.h>
/*第1步:声明print函数*/
extern void print(int, int);   // 等价于:extern void print(int a, int b)
extern void swap(int, int);   // 声明交换函数


int main(void) {
	/*第3步调用print函数*/
	print(100, 200);  //传递常亮给print函数,结果print函数的:x=100,y=200
	int a = 10, b = 20;
	printf("实参变量a,b的地址分别是:%p, %p\n", &a, &b);
	print(a, b);  /*调用print函数,传递变亮a, b的值,结果x=a=10,y=b=20*/	

	/*调用交换函数swap*/
	swap(a, b);
	printf("交换之后:a = %d, b = %d\n", a, b);

	return 0;
}


/*定义print函数和swap函数*/
void print(int x, int y) {
	printf("x变量和y变亮的地址分别是:%p, %p\n", &x, &y);
	printf("x变量的值和y变量的值分别是:%d, %d\n", x, y);
}  // 函数执行到这个花括号结束

void swap(int x, int y) {  // swap(a, b) = swap(10, 20):x=10, y = 20
	int z = x; // z=x=10
	x = y;	// x=20
	y = z; // y =10
	printf("x = %d, y = %d\n", x , y);	
}


结果:
x变量和y变亮的地址分别是:0xbf926100, 0xbf926104
x变量的值和y变量的值分别是:100, 200
实参变量a,b的地址分别是:0xbf926114, 0xbf926118
x变量和y变亮的地址分别是:0xbf926100, 0xbf926104
x变量的值和y变量的值分别是:10, 20
x = 20, y = 10
交换之后:a = 10, b = 20

形式4:有形参有返回值

返回值数据类型 函数名(形参变量的个数建议小于4个)
{
	函数语句;
	return 常量或者变量;
}
注意事项就是形式2和形式3的注意事项的合体
参考代码:function3.c
/*有形参有返回值*/
#include <stdio.h>

/*定义add函数*/
int add(int x, int y) {
	return x + y;
}

/*定义sub函数*/
int sub(int x, int y) {
	return x - y;
}

/*定义两个正数相加*/
unsigned int num(unsigned int c, unsigned int d) {
	return c + d;
}


int main(void) {
	int a = 200, b = 100;
	int ret = 0;
	unsigned int m = 10, n = 5;
	unsigned int ret1 = 0;
	/*调用add函数*/
	ret = add(a, b);
	printf("加的结果是%d\n", ret);
	/*调用sub函数*/
	ret = sub(a, b);
	printf("减的结果是%d\n", ret);
	/*调用num函数*/
	ret1 = num(m, n);
	printf("%d\n", ret1);
	return 0;
}

结果:
加的结果是300
减的结果是100
15

6.人生的第三个标准函数exit()

exit函数功能:立刻让程序退出,结束,和main函数中的return一个效果

使用格式:exit(int num);	// 如果程序结束并且告诉操作系统是异常结束,num=0
						// 如果程序结束并且告诉操作系统异常结束,num=负值
参考代码:exit.c
/*exit函数演示*/
#include <stdio.h>
#include <stdlib.h>  // 为了使用exit函数

void print(void) {
	printf("1\n");
	exit(0); // 程序立刻结束
	printf("2\n");
}


void print1(void) {
	printf("3\n");
	return; // 函数在此返回,由于返回值位void,所以return后面不用跟常量或者变量,直接返回就行
	printf("4\n");
}

int main(void) {
	print1();
	print();
	printf("5\n");
	return 0;
}

结果:
3
1

7. 函数和数组的拿点事儿

  1. 之前的代码都是研究函数和变量的关系,也就是如何通过函数访问操作变量

    也就是如何通过函数对变量分配的内存进行访问

    问:函数如何操作访问数组分配的内存呢?

    答:利用以下公式即可让函数和数组结合起来,通过函数能操作访问数组

  2. 函数访问数组的编程公式:

    /*定义访问数组的函数*/
    void 函数名(数组的首地址, .../*根据自己的需求添加其他形参变量*/)
    {
    	用"[]"和下标形式,可以查看数组元素值
    	用"[]"和下标的形式,可以直接修改数组元素的值
    }
    注意:此函数能直接访问数组的内存
    例如:
    void A(int a[], int len)
    {
    	//打印数组某个元素值
    	printf("%d\n", a[元素下标]);
    	// 修改元素值
    	a[元素下标] = 新值
    }
    注意:第一个形参int a[], a不是数组,如果是数组,由于没有指定元素个数,gcc肯定报错
    	这里a仅仅保存了数组的首地址而已。形式上目前来说必须这样写
    	使用时结合"[]"和下标来直接访问数组即可
    
参考代码:function_array.c
/*函数和数组那点事儿*/
#include <stdio.h>
/*a:保存数组的首地址*/

/*定义打印数组元素值的函数*/
void print(int a[], int len) {
	for(int i = 0; i < len; i++) {
		printf("a[%d] = %d\n", i, a[i]);
	}
}

/*定义扩大10倍的函数*/
void change(int a[], int len) {
	for(int i = 0; i < len; i++) {
		a[i] *= 10;
	}
}
/*定义交换数组中任意两个元素的函数*/
void swap(int a[], int m, int n) {   // m和n分别代表要交换的两个元素的下标
	int c = a[m];
	a[m] = a[n];
	a[n] = c;
}


int main(void) {
	int array[5] = {1, 2, 3, 4, 5};
	print(array, sizeof(array)/sizeof(array[0]));
	change(array, sizeof(array)/sizeof(array[0]));
	print(array, sizeof(array)/sizeof(array[0]));
	swap(array, 0, 1);// a[0] 和a[1]交换
	swap(array, 2, 3); //a[2] 和a[3]交换	
	print(array, sizeof(array)/sizeof(array[0]));

	return 0;
}
结果:
a[0] = 1
a[1] = 2
a[2] = 3
a[3] = 4
a[4] = 5
a[0] = 10
a[1] = 20
a[2] = 30
a[3] = 40
a[4] = 50
a[0] = 20
a[1] = 10
a[2] = 40
a[3] = 30
a[4] = 50

作业:设计两个函数,分别实现将数组中某个元素的某个比特位清0或者置1

在这里插入图片描述
关注公众号,获取更多精彩内容

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值