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列
语义:
-
a代表二维数组,包含5个元素,每个元素是一个一维数组
而一维数组中又包含3个元素(数字)
所以a二维数组名就是二维数组的首地址
-
a[0]代表二维数组a的第0个一维数组,a[0]就是第0个一维数组的首地址
它同样也等于a二维数组的首地址,即:a = a[0]
a[1]代表二维数组a的第1个一维数组,a[1]就是第1个一维数组的首地址
……
-
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
-
二维数组a分配内存总大小等于15个元素的内存,而每个元素右占4个字节,所以总共15*4 = 60个字节
-
二维数组中每个元素数字的访问需要用两层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 函数声明
-
函数声明的功能:告诉gcc编译器,将来这个函数可以给别人或者自己使用
函数声明是不会分配内存空间的
-
函数声明的语法:extern 返回值数据类型 函数名(形参表);
例如:extern int add(int x, int y);
注意:
-
extern关键字可以省略,由衷建议加上去(目的:提高代码可读性)
-
如果函数定义在函数调用之前,可以省略编写函数声明,否则必须
在函数调用的前面编写函数声明代码
-
函数名要符合标识符的命名规则
-
如果函数执行完毕,需要返回数字,这个数字的数据类型就是返回值的数据类型
如果函数执行完毕没有返回值,不需要返回数值,返回值数据类型写void关键字
例如:extern void add(int x, int y);
-
形参表就是定义了一堆变量而已,例如:int x, int y
用于保存接收调用函数时传递的参数,例如:add(a, b)
也就是变量a的值将来会赋值给变量x变量、
也就是变量b的值将来会赋值给y变量
形参表的变量名在函数声明时可以不用写:例如:extern int add(int, int);
如果函数不需要传递参数,形参表直接写void,例如:extern int add(void);
-
形参表变量的个数要小于等于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 函数定义
-
函数定义概念:就是一个函数的具体实现过程,编码过程,函数的定义又称函数封装或者函数实现
函数定义是会分配内存的
一旦实现完毕,将来别人或者是自己的代码就可以访问调用了
-
函数定义的语法格式:
返回值数据类型 函数名(形参表){ 一堆得函数体语句; } 例如: int add(int x, int y) { return x + y; }
-
函数定义的注意事项
-
函数定义的返回值数据类型,形参表要和函数声明时的返回值的数据类型,形参表保持一致
否则编译器gcc报错
-
函数执行完毕如果要返回一个数字,这个数字的数据类型,就是返回值的数据类型
如果函数执行完毕没有或者不需要返回值数值,返回值数据类型写void关键字
-
形参表就是一堆变量的定义,个数小于等于4个,保存调用函数残敌的参数数值
如果没有形参,也就是不需要传递参数写void
-
函数执行完毕如果要返回一个数字,用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 函数调用
-
函数调用的概念:俗称使用函数,调用函数,访问函数
-
函数调用语法:接收函数返回值的变量 = 函数名(实参表);
例如:
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. 函数和数组的拿点事儿
-
之前的代码都是研究函数和变量的关系,也就是如何通过函数访问操作变量
也就是如何通过函数对变量分配的内存进行访问
问:函数如何操作访问数组分配的内存呢?
答:利用以下公式即可让函数和数组结合起来,通过函数能操作访问数组
-
函数访问数组的编程公式:
/*定义访问数组的函数*/ 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
关注公众号,获取更多精彩内容