c语言学习笔记(数组与函数)

数组的概念

---数组是一组相同类型元素的集合。(其实就是多个类型相同的变量值放在一起定义)

        数组与变量一样都是c语言提供给用户操控内存的一种方式,都是在内存中开辟空间存放数据。区别是数组可以开辟自定数量的连续空间。空间的大小由元素数量决定。

数组的特点:

        数组中所有元素具有相同的数据类型;  

        数组创建后,不能改变大小。

        数组中的元素存储在内存中的存储单元是连续依次排列的

一维数组

定义数组

---类型+数组名字+元素数量 ;

类型  名称 [元素数量];  //相比变量的定义,数组加了[ ] ,中括号内存放的值是元素的个数。

int  name [20];     //定义了一个数组,元素类型是整型,起了name的名称,数组大小是20个元素

double weight [10];  //元素数量必须为整数。元素个数不能为0。c99开始,可以用变量来定义元素数量,数组一旦创建就无法改变大小。

  • 类型:数组中存放数据的类型(非数组类型),可以是:char、short、int、float等也可以是自定义的类型。
  • 数组名称:指的是数组的名字,根据实际情况,起一个方便阅读理解的名字。
  • 元素数量:指定数组的大小,根据实际需求指定大小即可。 元素的数量就是数组的大小。

数组的类型 

数组本身也是有类型的,数组是一种自定义类型。去掉数组名留下的就是数组的类型。

int name [20];      //数组name的类型是 int [20]

double weight [10];    //数组weight的类型是 double [10]

数组的初始化

---在数组创建时,给定一些初始值,被称为初始化。

数组初始化赋值使用大括号,将数据放在大括号中,每个元素用逗号隔开。

int arr[5] = {1,2,3,4,5};     //完全初始化,数组大小为5个元素,给予5个值。

int arr[5] = {1};       //不完全初始化,数组大小5,给予1个值,剩余的元素默认值为0。

所赋值的个数不能超过数组的大小。

变长数组

c99之前创建数组

int a1[10];

int a2[3+5];

int a3[]={1,2,3,4,5};

c99标准开始,c语言中引入变长数组的概念,可以使用变量来自定义数组空间大小。但数组被创建后就不能更改大小了。

int n = 0;

scanf(“%d”,&n);

int a[n];      //数组的大小由n的值来决定,一旦确定了n的值,就确定了变量空间的大小。

数组a的大小取决与变量n的值,编译器没法事先确定,只有运行时知道n是多少后才能确定数组a的大小。当确定了n的值并在内存中创建了数组a的空间后。数组的大小就被确定,不能更改

变长数组不能被初始化

一维数组的使用

下标

下标也被称为索引。

c语言规定,数组的每个元素按顺序进行编号。从0开始一直到最后一个元素。这个编号被称为下标。这样第一个元素的下标就是0,最后一个元素的下标是元素的个数减1。

下标引用操作符 

c语言在数组的访问提供了一个操作符[ ],叫做:下标引用操作符

通过下标引用操作符,我们可以轻松的访问到数组的元素的值。如访问第一个元素就可以使用a[0],访问第2个元素就可以使用a[1]。 

a[0];  //a数组下标为0的元素。   

a[5];  //a数组下标为5的元素

下标是从0开始计数,最大下标是元素个数-1。

通过下标访问数组

通过数组下标访问数组时,获取的是对应位置元素的值 

int a[10] = {1,2,3,4,5,6,7,8,9,10};   //数组初始化。

printf(“%d”,a[0]);   //通过第零个下标打印第一个元素

越界访问问题

int name[20];  //定义了一个数组,有20个元素。

name[20];  //使用下标号为20的元素。超出了数组定义的元素个数。超出19就会超出name数组在内存中开辟的空间,进行越界访问。

注意: 

  • 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读或写。
  • 程序运行后,越界的访问的数组可能造成问题,可能会导致程序崩溃。 segmentation fault  所以写程序时一定要保证程序使用有效的下标值:[0,数组的大小-1]
  • 数组的下标必须是整型,c语言中字符是整数类型也可以作为下标使用

数组的遍历

通过for循环产生数组的下标来访问所有的元素

int a[10] = {1,2,3,4,5,6,7,8,9,10};   //数组初始化。

int i = 0;      //定义i变量存放下标。

for (i=0;i<10;i++)    //同过for循环将所有下标遍历一遍。

        printf("%d ",a[i]);    //输出数组的所有元素。

数组的输入

通过scanf函数,输入值给数组的某一个元素。

int a[10] = {1,2,3,4,5,6,7,8,9,10};   

 scanf("%d",&a[0]);    // 通过scanf函数给数组的第一个元素输入值。

printf(“%d”,a[0]);   

输入值给数组的所有元素。

int a[10] = {1,2,3,4,5,6,7,8,9,10}; 

int i = 0;      //定义i变量存放下标。

for (i=0;i<10;i++)    //同过for循环将所有下标遍历一遍。

        scanf("%d ",&a[i]);    //通过循环输值给数组的所有元素。

for (i=0;i<10;i++)    //同过for循环将所有下标遍历一遍。

        printf("%d ",a[i]);    //输出数组的所有元素。

数组元素在内存中是连续存放的 

 通过访问数组元素在内存中的地址来探究数组在内存中的存放方式。

int a[10] = {1,2,3,4,5,6,7,8,9,10}; 

int i = 0;      //定义i变量存放下标。

for (i=0;i<10;i++)    //同过for循环将所有下标遍历一遍。

        printf("%p\n ",&a[i]);    // 地址的格式占位符是%p

 通过输出分析,地址的大小随着下标的增长而变大,且相邻的元素之间相差4(因为一个整型是4个字节)。

所以得出结论:数组在内存中是连续存放的。

sizeof计算数组元素的个数

sizeof是c语言的关键字,可以计算类型或者变量空间的大小。sizeof也可以计算数组空间的大小。单位是字节。

计算数组所占空间大小

int a[10] = {1,2,3,4,5,6,7,8,9,10}; 

printf("%d\n",sizeof(a));  //结果是40,计算数组所占内存空间的大小,单位是字节。

计算数组单个元素所占空间大小

int a[10] = {1,2,3,4,5,6,7,8,9,10}; 

printf("%d\n",sizeof(a[0]));  //结果是4,计算数组下标为零的元素所占内存空间的大小,单位是字节。

数组空间大小除以单个元素的空间大小,得到数组的元素个数。

int a[10] = {1,2,3,4,5,6,7,8,9,10}; 

int c = sizeof(a)/sizeof(a[0]);      //定义c变量来存储数组的个数

printf("%d\n",c);           //结果为10,表示有10个元素。

strlen计算字符串的长度。

如果数组元素是字符,求字符的个数可以用strlen()。需要头文件string.h。

strlen()计算字符串长度的时候,遇到\0就自动停止了,不会将\0计算在内

char a[]="hello world!";            

printf("%c",strlen(a));       //输出结果为12。

printf("%c",a[strlen(a)-1]);           //通过最后一个元素的下标获取元素的值,结果为‘!’

printf("%s",a);           //%s字符串格式占位符,直接打印hello world!

多维数组

二维数组就是将多个一维数组作为数组的元素。三维数组就是将多个二维数组作为数组的元素。二维数组以上的数组统称为多维数组

二维数组的创建

类型+名称+[常量值1][常量值2];

int a[3] [5];

通常理解为a是一个3行5列的矩阵。

二维数组初始化

二维数组也使用大括号进行初始化

int a [3][5]={1,2,3,4,5,  2,3,4,5,6,  3,4,5,6,7};  //完全初始化

int a [3][5] = {1,2};    //不完全初始化,前两个数是1和2,剩余的值默认用0补全

int a [3][5] = {{1,2},{3,4},{5,6,}}  //按照行初始化。每行的前两个值有输入,其他默认0

int a [ ] [5] = {1,2,3};   //初始化时可以省略行,但不能省略列。1行5列

int a [ ] [5] = {1,2,3,4,5,6,7};   //2行5列,第一行值为1,2,3,4,5第二行为6,7,0,0,0

int a [ ] [5] = { {1,2},{3,4},{5,6} };   //3行5列,每行前两个值有输入,其他值默认为0

二维数组的使用

二维数组的下标

二维数组是有行和列的,只要锁定了行和列就能锁定数组中的一个元素。

c语言规定,二维数组的访问也是使用下标的形式。行是从0开始,列也是从0开始。

int a [3][5]={1,2,3,4,5, 

                   2,3,4,5,6,

                   3,4,5,6,7};

printf("%d",a[0][0]);  //打印行下标0,列下标0的元素(第一行第一个)。结果为1

printf("%d",a[2][4]);  //打印行下标2,列下标4的元素(第三行第五个)。结果为7

数组的遍历,访问整个数组

for(int i=0;i<3;i++)  //循环行

{

        for(int j=0;j<5;j++)  //循环列

        {

        printf(“%d ”,a[i][j]);   //输出每一个元素

        }

}

数组输入并输出

for(int i=0;i<3;i++)      //循环行

{

        for(int j=0;j<5;j++)    //循环列

        {

                scanf("%d",&a[i][j]);    //输入值

        }

}

for(int i=0;i<3;i++)      //循环行

{

        for(int j=0;j<5;j++)    //循环列

        {

                printf("%d ",a[i][j]);    //输出值

        }

}

注意:a[i,j]不是一个正确的表达二维数组的方式,逗号运算符的值是最右边的表达式,所以a[i,j]就是a[j]

二维数组方便阅读的书写格式

int a[][5]={

        {0,1,2,3,4},  //可以直接写0,1,2,3,4,2,3,4,5,6然后由编译器按顺序存放。

        {2,3,4,5,6},  //带上大括号分行书写方便阅读。 最后带逗号不带逗号都行,

};

行数可以由编译器来数,列数是必须给出的。

每行一个{},逗号分隔

最后的逗号可以存在,有古老的传统

二维数组的元素在内存中也是连续存放的。

通过打印数组所有元素的地址来探究二维数组在内存中的存储方式。

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

for(int i=0;i<3;i++)

        for(int j =0;j<5;j++)

                printf("%p\n",&a[i][j]);   //打印地址的格式化占位符%p。

从输出的结果来看,每一行的每个元素都是相邻的,地址相差4个字节,跨行位置的两个元素之间也是差4个字节,所以二维数组中的每个元素都是连续存放的。

函数

函数(function)也被称为子程序。c语言中的函数就是完成某项特定的任务的一小段代码。

函数有特殊的写法与调用方法。

库函数

库函数是c语言标准库中提供的现成的函数,要使用库函数,必须包含对应的头文件。

库函数相关头文件:https://zh.cppreference.com/w/c/header

自定义函数

函数是一段代码,接收零个或多个参数,做运算,并返回一个或者零个值。

函数的定义:

---自定义函数由函数头、函数体组成。

函数头:返回类型,函数名,(参数表)

函数体:大括号+内部功能代码

例如自定义一个函数:函数作用是计算两个整数的和

int add(int x,int y)     //函数返回类型int,函数名add,()内两个变量可以接收两个参数。

{     

        return x+y;     //返回x与y的和(表达式的结果被返回),返回值的类型定义在函数头内。

}                         //函数的返回值会放在寄存器内然后再被调用的。

函数的使用

调用函数:函数名(参数值) 

调用自定义的加法函数;

int add (int x,int y)   //自定义一个加法函数

{

        return x+y;

}

int main()

{

        int a,b,c;  

        scanf(“%d %d %d”,&a,&b,&c);  //调用:scanf()库函数

        printf(“a与b的和是%d\n”,add(a,b));   //调用:add(a,b)函数。a与b的会按顺序传给自定义函数参数表内的变量x与y。然后经过自定义函数的内部功能,输出两个数的和。最终通过printf()库函数打印出来

        printf(“a与c的和是%d\n”,add(a,c));   //再次调用自定义函数;自定义函数优点,将功能封装方便重复调用。

        printf(“b与c的和是%d\n”,add(b,c));   //再次调用加法函数

        return 0;

}

      每个函数都有自己的变量空间,参数也位于这个独立的空间中,和其他函数没有关系。定义的变量等也是在自己的函数内部中,与其他函数没有关系。  

函数中逗号和逗号运算符的区分。

调用函数时的逗号是标点符号,不是运算符。如add(a,b);。而add((a,b),(a,c));内的a,b   a,c间的逗号才是运算符

函数的参数传递

调用函数时必须传递给它数量、类型正确的值。

可以传递给函数的值是表达式的结果,可以是

常量     add(10,20);

变量   add(a,b);

计算的结果     add(a+5,b);

函数的返回值     add(add10,20),a);

调用函数时,是将数值传给函数,并非变量等表达式。

自动类型转换

如果调用函数的时候,给的值和参数的类型不匹配,编译器会自动进行类型转换,以适配函数参数表的参数类型。但编译器将类型转换后的结果可能并不是你所期望的。

add(2.5,3.6);  //自定义加法函数接收的是整型,传参时传了浮点数就会发生类型转换。

return 语句

return后可以是一个数值,也可以是一个表达式,如果是表达式则先执行表达式,再返回表达式的结果。

return后也可以什么都没有,直接写return;适合函数返回类型为void的情况

return返回值和函数返回类型不一致时,系统会自动将返回的值转换为函数的返回类型。

return语句执行后,函数就彻底返回,后面的代码将不再执行。

非void类型函数必须要有返回值,如果函数中存在if等分支的语句,要保证每种分支都能有返回值供给函数返回使用,否则会出编译错误。例如只写了if(a>0)return1;没有写a<=0的情况就会报错

返回值可以赋值给变量,可以再传递给函数,甚至可以丢弃(调用函数但未使用返回值,例如返回值外还打印了一串字符,我们需要这串字符不需要返回值时,就可以不使用返回值)。

没有返回值的函数

void 函数名 (参数表)

void end(void)  //返回类型为void说明函数没有返回值,参数表内void,表示不需要参数。

{

        printf("游戏结束");    //调用printf()函数时,会在屏幕打印“游戏结束”。

}      //end()函数没有任何返回值。

不能使用带值的return,可以没有return。调用的时候不能做返回值的赋值。

调用时直接写end();即可

函数不需要传递任何参数,那么在参数表内写void 如:void end(void);如果不写void,编译器会认为不确定是否有参数,而且如果有参数传到函数参数表内会默认为int类型参数。

注意: 

()起到了表示函数调用的重要作用,即使没有参数也需要()

如果有参数,需要给出正确的数量和顺序

 自定义函数没有写返回类型时,默认为整型。

如果函数有返回类型,但函数体内没写return时,那就不能确定具体返回什么。

函数在设计的时候,一定要尽量功能单一,方便调用。

数组做函数参数

传参方式:

        数组传参时()内写数组名即可,会把数组在内存中的地址传过去。这与数值、变量传参有本质的不同。(数组传参在内存中地址用的是同一个,也就是说并没有创建新的空间存放数据。数值、变量传参时在内存中开辟了新的空间,不同空间不同地址。)

接收参数;

        接收数组的参数时接收的是数组的地址。(地址对应的就是数组的参数)

        参数表内要创建对应的类型属性空间。

例如:自定义函数将整型数组内容全部设置为任意整数,再自定函数打印数组内容。

void set(int x[ ],int y,int z) //创建设置数组内容函数  参数表(接收数组,接收元素个数,接收任意值)

{

        int sz = sizeof( x ) / sizeof(x [0] ) ;         //不可以在函数内求数组的大小。数组作为参数传递给自定函数时,它会退化为指针(数组地址)。所以sizeof(x)求的是指针本身的大小(在64位系统是8字节,在32位是4字节)。当sizeof(x)/sizeo(x[0])计算时,实际是指针大小除以数组元素的大小。结果肯定不是元素的数量。所以需要传元素数量进来。

        for(int i = 0;i<y;i++)   //遍历数组

                int x[i] = z;     // 将数组的每个元素设置为传过来的任意数.

}              //自定义函数内的数组与主函数中的数组是同一个(函数内的数组是通过指针将数组内容重新指向主函数中的数组)。所以在函数中改变了元素的值后,主函数中的数组元素的值也会被改变。这与普通变量的传值有很大的不同。

void print(int x[ ],int y)   //创建打印数组的函数  参数表(接收数组,接收元素个数)

{

        for(int i = 0; i<y;i++)

                printf("%d ",x[i]);

        printf("\n");    //如果连续调用函数时,每次打印完可以换行。

}

int main()

{

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

        int sz = sizeof( a ) / sizeof( a[0] ) ;    //定义变量sz用来存放元素个数

        int n = 0;           //创建变量n用来存放任意整数。

        scanf("%d",&n);

        set(a,sz,n); //调用set()函数,数组传参时只需要数组名即可。

        print(a,sz);           //调用自定义的print()函数并按顺序传递参数。

return 0;

}

函数参数表中参数如果是一维数组,数组大小可以省略不写,如果是二维数组 ,行可以省略,但是列不能省略。

自定义函数参数表中接收的数组,不是重新创建的数组,而是与主函数中的数组是同一个。

嵌套调用和链式访问

嵌套调用

嵌套调用就是函数之间的互相调用。

例如:输入年份,月份,输出天数,用函数实现。

bool leapyear(int y)            //自定义函数,判断是否是闰年  布尔类型需要头文件stdbool.h

{

        if(y%400==0 || y%100!=0 && y%4==0)

                return true;

        else

                return false;

}

int getday(int y, int m)         //自定义函数,通过年份,月份,获得天数

{

        int day[ ]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 30};//多输入一个元素使下标就是月份

        if(m==2 && leapyear(y))     //getday()函数内调用leapyear()函数就是嵌套调用。利用逻辑与短路特性,如果m==2不成立也就无需调用leapyear()函数。

                day[m]=29;      //如果是闰年,那么2月份是29天。

        return day[m];       //返回月份对应的天数

}

int main()         //主函数,要求输入年与月,输入天数

{

        int year month;

        scanf("%d %d",&year, &month);   //输入年份与月份

        printf("%d", getday(year,month));   //通过getday()函数获得天数,并打印

return 0;

}

 链式访问

将一个函数的返回值作为另外一个函数的参数,像链条一样将函数串起来就是函数的链式访问。

例如上面printf("%d", getday(year,month));将getday()函数的返回值作为printf()函数的参数

函数的声明

c的编译器子上而下顺序分析代码。所以函数要定义在主函数的上面。这样程序在运行时编译器就会知道所调用的函数是什么样的,要几个参数,参数是什么类型,返回什么类型等。

如果函数定义在函数调用的下面,那么调用函数前需要先进行声明。

如何声明:

返回类型  名称  (参数类型);

最简单的方法是将函数头复制到main函数前面并以分号;结尾。 这叫做函数的原型声明。

当程序运行时编译器会根据原型声明来确认函数的定义。如果声明与函数定义一致(返回类型,名称,参数个数及类型。),主函数正常运行,如果不一致会提示出错。

bool leapyear(int);//声明:返回类型 函数名 参数表内参数类型,参数的名可省略

int getday(int y, int m);//函数的声明:参数名虽然可以省略,但一般不省,方便阅读。

int main()         //主函数,要求输入年与月,输入天数

{

        int year month;

        scanf("%d %d",&year, &month);

        printf("%d", getday(year,month));   

return 0;

}

bool leapyear(int y)            //自定义函数,判断是否是闰年  头文件stdbool.h

{

        if(y%400==0 || y%100!=0 && y%4==0)

                return true;

        else

                return false;

}

int getday(int y, int m)         //自定义函数,通过年份,月份,获得天数

{

        int day[ ]={0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 30};

        if(m==2 && leapyear(y))     

                day[m]=29;      

        return day[m];       

}

        函数和变量都要满足:先声明后使用。 

        如果函数定义在主函数下面,并且未进行声明。那么主函数中调用自定义函数时,默认调用的函数为int类型。 

        C语言不允许定义函数内嵌套另一个函数的定义,只能调用或者声明。

        int a ,b,sum(int x,int b);//定义了整型变量a与b。声明了函数原型sum,sum需要两个int类型的参数,并且返回一个int类型的值。

递归

在c语言中,函数自己调用自己就是递归。

递归的限制条件

递归存在限制条件,每次递归调用之后越来越接近这个限制条件,当满足这个限制条件的时候,递归便不再继续。

如果没有限制条件,代码会陷入死递归,导致栈溢出(Stack overflow)

递归的执行逻辑

递归中的递就是递推的意思,归就是回归的意思

把一个大型复杂的问题,一层层的转化为一个与源问题相似,但规模较小的子问题来求解。

例如:输入一个整数,按照顺序打印整数的每一位。 输入 1234,输出1 2 3 4

void pt(int n)

{

        if(n>9)     //判断,如果是个位数就结束递归。

                pt(n/10);    //函数自己调用自己。知道达到限制条件为止。

        printf("%d  ",n%10); 

}

int main()

{

        int num = 0;

        scanf("%d",num);

        pt(num);

return;

}

如果传输n值为1234,那么递归的执行逻辑是:

pt(1234)  进入if判断大于9,执行pt(1234/10),待执行printf(“4”)函数;

pt(123)进入判断大于9,执行pt(123/10)待执行printf(“3”)函数;

pt(12)进入判断是否大于9,执行pt(12/10)待执行printf(“2”)函数;

pt(1)小于9,递推结束,开始回归。开始执行后面的代码;

pt(1)函数开始执行printf(“1”)代码,1%10打印1

pt(12)函数开始执行printf(“2”)代码,12%10打印2

pt(123)函数开始执行printf(“3”)代码,123%10打印3

pt(1234)函数开始执行printf(“4”)代码,1234%10打印4

最终输出1 2 3 4.

本地变量 

        函数的每次运行,就产生了一个独立的变量空间,在这个函数空间中的变量,是函数运行所独有的,称作本地变量。

        定义在函数内部的变量就是本地变量,也被称为局部变量,自动变量等。

        参数表的参数也是本地变量。

变量的生存期和作用域

生存期:变量的定义到变量的消亡(出作用域就消亡)

作用域:在(代码的)什么范围内可以访问这个变量,(这个变量可以起作用的范围)

对于本地变量,它的生存期和作用域是大括号内。

对于全局变量,它的生存期和作用域是整个工程项目。

本地变量的规则

1.本地变量是定义在块内(大括号内)的

        它可以是定义在函数的块内

        也可以是定义在语句的块内,如分支与循环语句块内所定义的变量。

        甚至可以随便拿一对大括号来定义变量。

2.程序运行进入这个块之前,其中的变量不存在,进入时才会被创建,离开这个块,其中的变量就消失。

3.块外面定义的变量在里面仍然有效。

4.块内外面定义了同名变量,块内将使用块内新定义的变量。

5.不能在同一个块里定义同名变量。

6.本地变量默认不会被初始化。

7.参数在进入函数的时候就会被初始化。

static修饰符

 static是c语言中的关键字,是静态的意思可以用来修饰局部变量、全局变量、函数。

函数内当变量被static修饰后,变量就不会随着函数调用的结束而销毁。

void test()     //自定义一个测试函数。

{

        static int i =0;    //使用static修饰局部变量i

        i++;

        printf("%d\n",i);  //i的值随着每次调用有累加效果。

}

int main()

{

        test();   //调用函数

        test();  //调用函数

        test();  //调用函数

        return 0;

}

static修饰局部变量改变了变量的生命周期,而生命周期的改变本质上是改变了变量的存储类型。

局部变量在内存中是存储在栈区的。被static修饰后存储到了静态区。存储在静态区的变量和全局变量是一样的,只有程序结束,变量才会销毁。但是作用域不变。 

什么是表达式

        对c语言来说表达式是一个非常宽的术语,很多东西都可以看作是表达式,单一的字面量,单一的变量,计算的结果,赋值,函数调用本身也是表达式。

        表达式是由运算符、操作数以及标点符号组合而成的式子,经计算后会得到一个值。

        常量本身就是固定的值,无需额外运算,所以是一种最简单的表达式。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值