C语言基础复习(八)

1.函数

1.1函数定义

数据类型符 函数名(形式参数名声明列表)

 {

        函数体

}

注意:

  • 函数是独立的功能模块, 在函数定义时, 函数体内绝对不能有其他函数的定义, 但可以有其他函数的调用.

  • 函数调用作为运算数进而构成表达式,c = add(add(a,b),b);这类函数调用涉及的函数是具有返回值的函数,函数返回值的数据类型一般属于基本数据类型, 绝不能是void类型

  • 函数声明不同于函数定义, 函数声明只是在编译时告知系统被调函数在运行中将要调用声明中的函数, 确保对被调函数返回值的处理. 函数声明没有函数体

  • 当一个函数被多个主调函数调用时, 各主调函数都要对同一个被调函数进行多次函数声明(这里指函数仅在内部声明).

  • 函数定义是明确函数功能, 在同一个程序中, 不能有同名函数的多个函数定义, 即在源文件中, 函数定义必须唯一, 但函数声明可以不唯一.

  • 调用函数声明可以在主调函数内, 也可以在主调函数外, 这两种形式的声明其被调函数的有效范围不同.

  • 主调函数内声明的被调函数只限于该主调函数内有效, 而且这些主调函数内无须再对该被调函数进行声明

  • 为了保证程序的可读性, 将所有的被调函数在函数外声明, 并放在头文件处, 其有效范围就是整个文件的所有主调函数.

  • 在函数定义时指明函数参数和函数返回

  • 函数调用时, 主调函数把数据传递给被调函数, 被调函数处理完数据后, 把处理结果返回给主调函数

1.2参数

1.2.1实际参数

在函数调用中, 紧跟函数名后的括号内所带的参数。实参可以是常量、变量、表达式。

    float x, y, z;
	x=2;
	y=3;
	z=add(x,y);//x,y为实际参数

1.2.2形式参数

在定义函数时, 紧跟函数名后的括号内的参数, 表明在函数调用时, 所需特定数据类型的形参和形参个数。

void multi(int a, int b)//a,b为形式参数
{
	int c;
	a=a+1;
	b=b+1;
	printf(“a=%d, b=%d\n”, a, b);
	c=a*b;
	printf(“%d*%d=%d\n”, a, b, c);
}
  • 在函数调用前, 形参不占据任何数据单元
  • 在形参调用后, 根据形参的数据类型, 在内存数据区中为形参开辟相应的数据单元, 并接受从实参传来的数据
  • 在调用结束后, 形参的数据单元由系统撤销、回收
  • 形参变量为动态变量. 在函数调用时, 形成形参变量数据单元, 接收实参数据单元传来的值; 在被调函数执行结束后, 释放形参的变量单元. 由于形参变量与实参变量占据不同的数据单元, 因此形参和实参可以同名
  • 因为形参与实参处于不同的数据单元,所以形参的改变不影响实参的值。
  • 结构体变量、共用体变量、枚举变量与基本类型变量一样, 可作为函数参数, 并具有与形参和实参相同的结合方式.

  • 数组也可以作为函数参数, 数组作为函数参数的形式有两种,数组元素作为函数参数和数组名作为函数参数.

1.3函数返回

return 语句结束被调函数的执行, 返回主调函数。在结束被调函数时, 可以将返回值给主调函数。

  • 需要从被调函数中得到一个值, 该函数至少有一个return语句. 先遇到哪个return语句就返回该return语句后的值, 并结束调用函数执行.
    int max(int x, int y)
	{
		if(x>y) return x;   //x>y, 则返回x, 后面的语句不执行
		else return y;
	}

  • 当定义函数指明的返回值数据类型与return语句中运算数的数据类型不一致时, 函数调用后只能得到定义函数时指明的数据类型值
    int max(float x, float y)
	{
		float z;
		z=x>y?x:y;
		return z;	//z定义时为浮点型, 但返回int型	
    }

  • 若函数中没有return语句, 则当调用该函数后, 得到一个已指明返回值类型的随机值, 并不是不返回值.

    int max(float x, float y)
	{
		float z;
		z=x>y?x:y;	
	}

  • 在定义函数时, 指明函数返回值的数据类型为空类型, 该调用函数就不能得到任何值.

    void print_value(float w)
	{
		printf(“%5.3f\n”, w);	
		return;
	}

  • 在定义函数时, 没有指明返回值的数据类型, 则该函数隐含返回值为整型, 为了增强程序的可读性, 最好指明返回值的数据类型.
    max(float x, float y)
	{
		return(x>y?x:y); //结果为浮点型, 但返回整型	
	}

1.4函数有效范围

1.4.1内部函数

    static 数据类型符 函数名(形参列表) 
	{
        函数体
    }

static声明

函数只能被本文件的其他函数调用, 而不能被其他文件的函数调用, 即内部函数的最大有效范围也不能超过本文件中的函数调用.

  • 不同文件中同名的内部函数互不干扰.
  • 文件file2.c定义了函数fun为内部函数, 文件file1.c中任何函数都不能调用函数fun, 但可以被文件file2.c内的函数调用, 保证了函数fun的安全性
  • 若文件file1.c内有函数fun的定义, 则这两个函数fun互不相同, 即内部函数不能跨文件调用.

1.4.2外部函数

    extern 数据类型符 函数名(形参列表) //extern可以省略
	{
        函数体
    }

函数不仅可以被本文件的其他函数调用, 而且也可以被其他文件中的函数调用, 即外部函数的有效范围可以是所有文件的函数调用.

  • 在文件file1.c中对文件file2.c的函数fun进行外部函数声明, 因此file1.c中任何函数都可以调用函数fun, 也可以被文件file2.c内的函数调用.
  • 在软件系统开发时, 使用外部函数可以更好地分工协作. 每人完成一定量的函数定义, 然后各自编译, 最后链接成统一的可执行文件.

2.文件包含

  • C语言编译系统在对C源程序进行编译之前, 根据编译预处理命令, 自动对源程序进行一些处理, 编译预处理, 有利于提高C源程序的模块化, 完善程序的可读性和可移植性.
  • 编译预处理命令以#开头, 包括文件包含(#include)、宏定义(#define)和条件编译(#if)
  • 编译预处理命令不是语句, 一般出现在源程序开头处,不加分号(;)
  • 文件包含预处理命令:#include “源程序文件名”或#include <源程序文件名>

  • 有文件file1.cfile2.c, 当对文件file1.c进行编译前, 编译系统把文件file2.c自动插入到文件file1.c, 再进行编译, 最终形成目标文件file1.obj, 进一步链接成可执行文件file1.exe.
  • 文件包含虽然可以连接不同文件, 但是只能对源程序文件进行链接(如上,只对file1进行连接)

  • 文件包含后只生成一个目标文件, 并没有生成被包含文件的目标文件

  • 文件包含预处理后只有一个源程序文件, 因此所有文件中的函数都是内部函数, 没有外部函数. 即内部函数与外部函数只是对后续各文件独立编译后链接而言的

  • 无论有多少个文件进行连接,最多只能有一个主函数

在C语言中,每个独立的源文件(.c文件)都可以包含一个主函数(main函数)。在连接(linking)多个源文件时,确保只有一个主函数的方法是通过使用编译和链接两个步骤,并在链接阶段指定一个入口点。

具体来说,将每个源文件编译为单独的目标文件(.o文件),然后将这些目标文件链接在一起生成最终的可执行文件。在链接阶段,可以通过指定一个入口点来明确指定程序的起始位置,只有这个入口点所在的源文件中的主函数将成为可执行文件的主函数。

以下是一个示例:

1.假设有两个源文件file1.cfile2.c,它们都包含了主函数main

file1.c:

#include <stdio.h>

int main() 
{
    printf("This is the main function in file1.c\n");
    return 0;
}

file2.c:

#include <stdio.h>

int main() 
{
    printf("This is the main function in file2.c\n");
    return 0;
}

2.在命令行中,使用编译器(如gcc)将每个源文件编译为目标文件:

gcc -c file1.c -o file1.o
gcc -c file2.c -o file2.o

3.在链接阶段,使用编译器指定入口点:

gcc file1.o file2.o -o myprogram

在这个例子中,我们通过使用gcc编译器将file1.cfile2.c分别编译为file1.ofile2.o两个目标文件。然后,在链接阶段,通过指定file1.o作为入口点来决定哪个主函数将成为最终可执行文件的主函数。

在这种情况下,生成的可执行文件myprogram将使用file1.c中的main函数作为主函数,而file2.c中的main函数将被忽略。

3.变量有效范围与存储类别

3.1局部变量

  • 任何函数都不能访问不在有效范围内的变量。

  • 形参是内部变量。

  • 在不同有效范围内的内部变量可以同名,因为对应不同的存储单元,所以这些变量之间并没有关联

  • 同名内部变量有效范围出现完全覆盖时,被覆盖的内部变量访问优先权高于覆盖内部变量。

  • 同一个有效区域内的变量只能唯一,不能有同名的变量。

3.2全局变量

  • 全局变量在程序的全部执行过程中占用存储单元,而不是仅在需要时才开辟单元

  • 使用全局变量过多,会降低程序的清晰性,人们往往难以清楚地判断出每个瞬时各个外部变量的值。在各个函数执行时都可能改变外部变量的值,程序容易出错。因此,要限制使用全局变量

  • 外部变量影响了函数的独立性,破坏了程序的模块化,同时函数要受外部变量当前值的影响,因此必须跟踪外部变量的变化,这样就降低了程序的可读性。

3.3举例

# include <stdio.h>
int b=100;
void fun1( )
{
           printf("内部变量1 b=%d \n", b);
           b*=10;                         //此时的b是外部的b,改变其值
}
int a=10;
void main()
{
           printf("内部变量2 a=%d \n", a);
           fun1( );
           printf("外层变量2 b=%d \n", b);
}
//内部变量2: a=10
//内部变量1: b=100
//外部变量2: b=1000

#include <stdio.h>
int b=100;      //空间作用域一直从这到最后(全局变量)
void fun2()
{
	       int b=1;//空间作用域在此函数内,被上面的全局变量覆盖,所以优先使用被覆盖的局部变量
           printf("内部变量1 b=%d \n", b);//这里的b是局部变量
           b*=10;                        //改变的也是局部变量
}                                        //函数调用完后,局部变量就被销毁,不在栈中。
int a=10;                                 //空间作用域为从这到后面
void main()
{
	       int a=100;                   //局部变量
           printf("内部变量2 a=%d \n", a);//打印局部变量(就近原则)
           fun2();                       //跳转到上面函数定义
           printf("外层变量2 b=%d \n", b);//打印栈中还有的b
}
/*
外部变量2: a=100
外部变量1: b=1
外部变量2: b=100
*/

如果不容易理解,那么可以采用汇编的方法来理解,函数的调用要开辟自己的栈帧,开辟栈帧的时候要为非静态局部变量开辟空间,函数执行完成后有销毁非静态局部变量,因此函数每次访问(取值或者赋值)时,都会在栈帧中向上(高地址)寻找变量,如果在本函数中有,那么就访问最近的这个,有点像就近原则,但是一旦这个函数(或者时复合语句)结束后,本函数内所有的非静态局部变量就被销毁了。

汇编解释:

栈(Stack)是一种采用“先进后出”方式进行访问的一块存储区, 用于嵌套过程调用。从高地址向低地址增长。

3.4变量的存储类别

3.4.1静态存储

  • 静态存储方式是指在程序运行期间由系统分配固定内存存单元
  • 全局变量采用静态存储方式,在程序开始执行时给全局变量分配存储区,程序执行完毕释放。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放

3.4.2动态存储

  • 动态存储方式是在程序运行期间根据程序的需要动态分配内存数据单元
  • 函数中定义的变量,在函数调用开始时分配动态存储空间,函数结束时释放这些空间。在程序执行过程中,这种分配和释放是动态的

3.4.3auto

  • 函数中的形参和在函数中定义的变量(包括在复合语句中定义的变量),都属于此类
  • 在调用该函数时,系统给这些变量分配存储空间,在函数调用结束时就自动释放这些存储空间。因此这类局部变量称为自动变量
int f(int a)   
{
     auto int b,c=3;  //auto可以省略
      …
}

3.4.4static

  • 希望函数中的局部变量值在函数调用结束后不消失而继续保留原值,即其占用的存储单元不释放,在下一次该函数调用时,该变量已有值,就是上一次函数调用结束时的值。这时就应用关键字static指定该局部变量为“静态局部变量”
#include <stdio.h>
void fun() 
{     int a = 10;
      static int b = 10;
      a++;   b++;
      printf("a=%d  b=%d\n", a,b); 
}
void main()
{     int i;
      for(i=1; i<= 5; i++)
      {    printf("No:%d  ", i); 
            fun();
      }
}                                  /*
                                     No:1 a=11 b=11
                                     No:1 a=11 b=12
                                     No:1 a=11 b=13
                                     No:1 a=11 b=14
                                     No:1 a=11 b=15
                                   */
  • 静态局部变量属于静态存储类别,在静态存储区内分配存储单元。在程序整个运行期间都不释放
  • 自动变量(即动态局部变量)属于动态存储类别,占动态存储区空间而不占静态存储区空间,函数调用结束后即释放

  • 静态局部变量是在编译时赋初值的,即只赋初值一次,在程序运行时它已有初值。以后每次调用函数时不再重新赋初值而只是保留上次函数调用结束时的值。

  • 而对自动变量赋初值,不是在编译时进行的,而是在函数调用时进行,每调用一次函数重新给一次初值,相当于执行一次赋值语句。

  • 如在定义局部变量时不赋初值的话,则对静态局部变量来说,编译时自动赋初值0(对数值型变量)或空字符(对字符变量)。

  • 而对自动变量来说,如果不赋初值则它的值是一个不确定的值。这是由于每次函数调用结束后存储单元已释放,下次调用时又重新另分配存储单元,而所分配的单元中的值是不可知的

  • 虽然静态局部变量在函数调用结束后仍然存在,但其他函数不能引用它的。因为它是局部变量,只能被本函数引用,而不能被其他函数引用

  • 用静态存储要多占内存(长期占用不释放,而不能像动态存储那样一个存储单元可供多个变量使用,节约内存),而且降低了程序的可读性,当调用次数多时往往弄不清静态局部变量的当前值是什么。因此,若非必要,不要多用静态局部变量。

3.4.5register

CPU运行时,需要的操作数大部分来自寄存器

如需要从(向)存储器中取(存) 数据时,先访问cache,如在,取自cache

如操作数不在cache,则访问主存,如在主存中,则取自主存
如操作数不在主存,则访问硬盘,操作数从硬盘中读出→主存 →cache

所以:如果把数据直接存储到寄存器中, 则数据没有在内存与寄存器之间交互传输, 大大提高了计算机的执行效率, 因此把使用频率高的变量设置为寄存器储存方式为宜.

3.4.6extern

  • 如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件结束
  • 如果由于某种考虑,在定义点之前的函数需要引用该外部变量,则应该在引用之前用关键字extern对该变量作“外部变量声明
  • 在任一个文件中定义外部变量,而在另一文件中用extern该变量作外部变量声明
  • 对一个变量的属性可以从两个方面分析,一是变量的作用域,一是变量的生存期。

4.数据位运算

4.1移位运算

《运算数》<<移动次数

移位复合赋值运算:

4.2按位取反

《~》《运算数》

char a=~9;
printf("%d\n",a);

a的结果为-10

4.3按位与

《运算数1》&《运算数2》

char a=9,c=5,c;
c=a&b;

C的结果为1

4.4按位或

《运算数1》|《运算数2》

4.5按位异或

《运算数1》^《运算数2》

4.6优先级

4.7常用运算

4.8位域数据

所谓位域,就是把把一个字节按照二进制位,划分成不同区段,每个区段包含连续的若干二进制位。

4.8.1位域变量定义

《struct》《位域类型名》

{

        《数据类型符1》《位域名1》《:位域长度

        ……

}

struct bs
{
    int a:8;
    int b:2;
    int c:6;
}

注意:

  • 位域成员所属的数据类型必须是int,unsinged int 
  • 一个位域成员必须存储在一个字节中,不能跨越两个字节
  • 由于一个位域不允许跨两个字节,因此长度不能超过8
  • 位域可以无位域名,只用于填充或调整二进制位的位置,但无名位域没有标识符,无法使用。

4.8.1位域变量的访问

《位域变量名》《.》《位域名》

#include<stdio.h>
int main()
{
	struct bs
	{
		unsigned int a:1;//最大存1 
		unsigned int b:3;//最大存111(7) 
		unsigned int c:4;//最大存1111(15) 
	} bit,bits[2];
	int i;
	bit.a=1;
	bit.b=5;//101
	bit.c=12;//1100
	bits[0]=bit;
	bits[1]=bits[0];
	printf("%u,%u,%u\n",bit.a,bit.b,bit.c);
	for(i=0;i<2;i++)
	{
		printf("%u,%u,%u\n",bits[i].a,bits[i].b,bits[i].c);
	}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

背水

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

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

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

打赏作者

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

抵扣说明:

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

余额充值