-
2021-05-24 07:02:59
织梦多语言站点,{dede:include filename=''/}引入问题
织梦模板include插入非模板目录文件出现"无法在这个位置找到"错误的解决办法 以下是dede V55_UTF8 查dede include标签手册 (3) include 引入 ...
SQL Server小技巧【1】
1.SQL防止修改数据时引起多用户并发,当一条数据被一个用户锁定的时候其他用户将无法修改,除非将其释放. UPDATE TABLENAME WITH(ROWLOCK) SET 字段='Value' W ...
Python自动化中的键盘事件
1) from selenium import webdriver 加载模块 2) b = webdriver.Friefox() 打开浏览器 3) b.get(‘http://xxxxxxx’) 打 ...
JAVA自学笔记25
JAVA自学笔记25 1.GUI 1)图形用户接口,以图形的方式,来显示计算机操作的界面,更方便更直观 2)CLI 命令行用户接口,就是常见的Dos,操作不直观 3) 类Dimension 类内封装单 ...
python 绝版线程池
2.绝版线程池设计思路:运用队列queue a.队列里面放任务 b.线程一次次去取任务,线程一空闲就去取任务 import queueimport threadingimport contextlib ...
java的acm输入输出格式+大数语法
1.类名称必须采用public class Main方式命名 2.多组输入,读取到文件尾 Scanner scan=new Scanner(System.in); while(scan.hasNext ...
Asp.Net时间方法大全
DateTime dt = DateTime.Now; //当前时间 DateTime startWeek = dt.AddDays(- Convert.ToInt32(dt.DayOfWeek.To ...
关于css定位
关于定位要弄清一个概念,那就是"文档流" 什到是文档流 说得简单一点 就是 自上而下 从左到右 生成顺序 关于html元素又分为两种 块状元素 内联元素 块状元素:单独占一行 内联 ...
jqgrid单元格合并
如何利用Visio设计一个系统的结构图
首先建立一个空的vison列表 添加图形和连接线 托选一个矩形块到操作台上,并进行底色填充 选择有向线段1拖到矩形模块上,此时有向线段1会自动吸附到矩形的中点处. 此时按下图操作即可取消,自动吸附 托 ...
更多相关内容 -
C语言-函数的调用
2022-05-12 19:20:12C语言-函数的调用目录:
一、函数名作为函数的输入参数二、回调函数
1、回调函数的引入
2、回调与普通函数的调用
3、回调函数的作用
4、回调函数的程序编写
--------------------------------------------------------------------------------------------------------------
一、函数名作为函数的输入参数
函数参数传递分为两种,一种是值传递,一种是地址传递。一般我们传递时用的是地址传递。因为,若是采用值传递的话,比如我们传递一个数组 double a[100],则在调用函数的时候。编译器会把这整个数组复制到函数中,这样使用的空间是100*sizeof(double)=800.若是我们只传递数组名 a 这个地址的话,那么复制进去的空间只有 64/8=8 这么多(假设计算机是64位的)。这样比较下来,就有了100倍的差距,是不是很吓人。所以,不管是函数作为参数,还是数组,结构体什么的,我们一般都用地址传递,而不用值传递。
--------------------------------
1)函数地址如何传递
先说一次传递一个函数的: 我们先定义一个函数
double add(double x, double y)
{
return x + y;
}
然后接着建立一个函数指针 double (*pf)(double,double)=add; //这里(*pf)的括号不能省,不然就不是函数指针了
我们现在有一个函数:
double calculate(double x1, double y1, double(*f)(double, double)) //函数调用里面传递 函数指针数组 的方法
{
cout << "add:" << (*f)(x1, y1) << endl;
return 1;
}
然后我们来进行值传递
int x = 2; y = 1;
calculate(x, y, pf);
最后可以得到输出的结果是 2+1=3。
这是最基本的,下面讲我要说的重点,就是一次传递多个函数进去。想传递多个函数进去,我们要建立一个函数数组 。先定义两个函数:
double add(double x, double y)
{
return x + y;
}
double add2(double x, double y)
{
return x - y;
}
然后建立函数数组并赋值 double (*pf[2])(double,double) = { add, add2 };
接着传递给上面定义的calculate函数。调用方式为:calculate(x, y, pf);
calculate函数的接收方式应为:double calculate(double x1, double y1, double(**f)(double, double)) //传递的pf是一个数组的数组名且本身也是一个指针,即为二重指针或者double calculate(double x1, double y1, double(*f[])(double, double))
最后给出完整的代码
#include "iostream"
using namespace std;
double add(double, double);
double add2(double x, double y);
double calculate(double x1, double y1, double(**f)(double, double)) //函数调用里面传递 函数指针数组 的方法
{
cout << "add:" << (*f[0])(x1, y1) << endl;
cout << "add2:" << (*f[1])(x1, y1) << endl;
return 1;
}
int main()
{
int x, y;
double (*pf[2])(double,double) = { add, add2 };
x = 2; y = 1;
calculate(x, y, pf);
system("pause()");
return 0;
}
double add(double x, double y)
{
return x + y;
}
double add2(double x, double y)
{
return x - y;
}
在函数void function()中,需要将另外一个函数double input()的函数名作为输入参数。
定义函数指针:
typedef double (*P)(int);
Note:红色字体部分需要注意,函数input()所有的输入参数类型都需要包含在内。例如 double input(double u[5], int num, double x),则在定义时写作 typedef double (*P)(double*,int,double)。
此时函数作为一种类型,可以直接被其它函数调用。
调用格式:函数声明中定义 void function(P input),调用function(input)即可。例子:
#include "stdio.h"
#include "string.h"
typedef int (*P)(int);
void function(P input);
int input(int a);
int main()
{
function(input);
return 0;
}
void function(P input)
{
printf("ok");
}
int input(int a)
{
return a+5;
}
typedef int (*p) (int *p);的说明:
定义一个函数指针类型(注意是类型)p,指向一个函数,该函数接受一个参数int *型,返回int。也就是说,有这样的定义:
typedef int (*p) (int *p);
int foot(int *); //声明,foot在其它地方定义
可以这样使用:p pf = foot; //&foot也可以,一个意思
--------------------------------------------------------------------------------------------------------------
二、回调函数
1、回调函数的引入
应用程序需要采集硬件层的数据,比如串口接收数据、按键采集、ADC值采集。这种硬件层的数据怎么通知应用层来拿,或者怎么主动给它?我们以往最简单粗暴的方式是不是就是用一个全局变量,比方说硬件层串口接收到数据来了,那么我们把数据丢到数组里,然后把接收完成全局变量标志位置1。比方说全局变量名为RcvFlag,然后应用层程序会轮询判断RcvFlag==1?是的话就开始把数组里的数据取出来解析。很多人就会说了,你看我用这种方法照样能实现功能啊,为什么还要学习别的架构。这样做当然可以实现功能,但是会存在移植性很差的问题。----------------------------------------------------------
2、回调与普通函数的调用
1)普通函数的调用调用程序发出对普通函数的调用后,程序执行立即转向被调用函数执行,直到被调用函数执行完毕后,再返回调用程序继续执行。从发出调用的程序的角度看,这个过程为“调用-->等待被调用函数执行完毕-->继续执行”。--------------------------------2)回调函数的调用调用程序发出对回调函数的调用后,不等函数执行完毕,立即返回并继续执行。这样调用程序和被调用函数同时在执行。当被调函数执行完毕后,被调函数会反过来调用某个事先指定函数,以通知调用程序:函数调用结束。这个过程称为回调(Callback),这正是回调函数名称的由来。带参数的回调函数举例:#include "stdio.h"int call_back1(int value1) // 被调用的函数1{printf("This is call_back1,value = %d\n",value1);return 0;}int call_back2(int value2) // 被调用的函数2{printf("This is call_back2,value = %d\n",value2)return 0}int handle_call_back(int value,int (*call_back)(int)) // 回调函数{call_back(value);return 0;}int main(void) //主函数{int a = 10;int b = 20;handle_call_back(a,call_back1); // 函数作为参数被调用handle_call_back(b,call_back2); // 函数作为参数被调用return 0;}----------------------------------------------------------3、回调函数的作用那么在讲回调函数之前呢,对于函数调用呢我一般分为2种类型:1)输出型不知道大家有没有用过C语言自带的一些库函数,比如说sizeof()获取数据长度的关键词,memcpy()是内存拷贝函数,我们调用这个函数之后呢就能完成相应的功能。还有我们基于单片机的一些程序函数,比方说控制LED点亮熄灭、继电器吸合断开、LCD驱动等。那么这些呢,我一般称为输出型的函数。输出型函数我们是主导的角色,我们知道什么时候该调用它。--------------------------------2)输入型输入型也称为响应式的函数。比方说接收串口的数据,不知道什么数据什么时候来。再比方说按键检测的函数,我们不知道什么时候会按下按键,那么这些就要定义成响应式函数来实现,而响应式函数就可以用回调函数来实现。所以通过这两个种类型的分析我们就可以知道,回调函数基本是用在输入型的处理中。比方说,串口数据接收、按键检测、ADC值采集,ADC值也是输入到单片机里的,单片机是处于从机角色。那么它们输入的时间节点都是未知的,这些就能够用回调函数来处理。回调函数还有一个作用就是为了封装代码。比如说做芯片或者模组的厂家,我们拿典型的STM32来举例,像外部中断、定时器、串口等中断函数都是属于回调函数,这种函数的目的是把采集到的数据传递给用户,或者说应用层。所以回调函数的核心作用:(1)把数据从一个.c文件传递到另一个.c文件,而不用全局变量共享数据这么LOW的方法。(2)对于这种数据传递方式,回调函数更利于代码的封装。----------------------------------------------------------4、回调函数的程序编写前面说了很多概念性的东西,可能大家也比较难理解,回调函数最终呢是靠函数指针来实现的。那么我这里通过一些模拟按键的例子来演示下怎么回通过调函数来处理它们。下面是我们用C-Free模拟,参考例程搜索我的百度网盘“CallBack”。从模块化编程的思想来看,整个工程分为2个部分,应用层main.c文件,硬件层key.c和key.h文件。int main(int argc, char *argv[ ]){KeyInit( );KeyScanCBSRegister(KeyScanHandle);KeyPoll( );return 0;}KeyInit( ); //key.c文件的按键初始化函数KeyScanCBSRegister(KeyScanHandle); //key.c的函数指针注册函数。这个函数可能大家会有点蒙,想理解这个回调函数注册函数,要先从硬件层(key.h)头文件的函数指针定义说起,具体看下图。这里自定义了一个函数指针类型,带两个形参。然后我们在key.c这个文件里定义了一个函数指针变量。
重点来了,我们就是通过这个函数指针,指向应用层的函数地址(函数名)。具体怎么实现指向呢?就是通过函数指针注册函数。
这个函数是在main函数里调用,使用这种注册函数的方式注册灵活性也很高,你想要在哪个.c文件使用按键功能就在哪里调用。
这里要注意,main.c这个文件要定义一个函数来接收硬件层(key.c)过来的数据。这里定义也不是乱定义的,一定要和那个自定义函数指针类型返回值、形参一致。
然后把这个函数名字直接复制给KeyScanCBSRegister函数的形参就可以了。
这样调用后,我们key.c文件的pKeyScanCBS这个指针其实就是指向的KeyScanHandle函数。
也就是说执行pKeyScanCBS的时候,就是执行KeyScanHandle函数。那具体检测按键的功能就是KeyPoll函数,这个在main函数里调用。
当检测到键盘有输入以后,最终会调用pKeyScanCBS。最终执行的是main.c文件的KeyScanHandle函数。所以,我们来看下输出结果。
下面我再给大家捋一捋编写和使用回调函数的流程:
自定义函数指针,形参作为硬件层要传到应用层的数据。
硬件层定义一个函数指针和函数指针注册函数。
应用层定义一个函数,返回值和形参都要和函数指针一致。
应用层调用函数指针注册函数,把定义好的函数名称作为形参传入。
--------------------------------------------------------------------------------------------------------------
-
C语言函数在内存中的调用机制
2020-05-31 11:55:591.首先说单文件编程(就是所有的代码都在主函数main.c中编写)函数是如何调用的,再引入多文件编程函数如何调用的 //main.c中的代码如下 #include <stdio.h> int add(flaot x,float y) { return (x+y); } int ...1.首先说单文件编程(就是所有的代码都在主函数main.c中编写)函数是如何调用的,再引入多文件编程函数如何调用的
//main.c中的代码如下 #include <stdio.h> int add(flaot x,float y) { return (x+y); } int main(void) { flaot a,b; flaot c; a=2.2; b=3.1; printf("我要调用加法函数\n"); c=add(a,b); printf("%f\n",c); reuturn 0; }
图形解释上边的main.c中的代码在内存中运行的过程
首先执行main函数,在内存中开辟main栈,然后在main栈中存放在main函数中变量,当执行到add(a,b)这里时,程序又在内存中开辟add栈,然后在add栈中存放add函数中的变量,add函数执行完以后,立刻返回调用该函数的位置,然后add栈被销毁了,在内存中就没有了。
说明:
1.程序中的x和y是形式参数,而a和b是实际参数,x和y在程序编译的时候并不分配内存空间,只有当实际参数a->x,b->y以后才分配内存空间存储变量的值。
2.add函数是定义在main函数的前边的,所以直接调用不用声明函数(函数声明后边会讲到,你可以这么记住它,这就是规则)。如果add函数定义在main函数后边的话,应该在main函数之前先声明add这个函数。如下代码。#include <stdio.h> int add(flaot x,float y);//函数声明记得加分号 int main(void) { flaot a,b; flaot c; a=2.2; b=3.1; printf("我要调用加法函数\n"); c=add(a,b); printf("%f\n",c); reuturn 0; } int add(flaot x,float y) { return (x+y); }
3.函数声明就是告诉编译器这个函数在本文件(main.c文件)的后边定义或者在其它.c文件(涉及多文件编程后边会讲到)中定义的,请你不要报错,编译器看你声明了这个函数了就不会报错了。
2.接下来讲解多文件中编程函数调用以及头文件的问题
首先建立一个c工程,这个工程包含:main.c、fun1.c、fun1.h、fun2.c、fun2.h、fun3.c、fun3.h。
//main.c中代码 #include "fun1.h"//将这个头文件里边的内容复制一份过来替换到fun1.h这个位置 #include "fun2.h"//将这个头文件里边的内容复制一份过来替换到fun2.h这个位置 #include "fun3.h"//将这个头文件里边的内容复制一份过来替换到fun3.h这个位置 int main(void) { fun1(); fun2(); fun3(); return 0; } //fun1.c中代码 #include<stdio.h>//加这个头文件是因为下边用到了printf函数,以下几个也是一样的道理 void fun1(void) { printf("我是函数1\n"); } //fun2.c中代码 #include<stdio.h> void fun2(void) { printf("我是函数2\n"); } //fun3.c中代码 #include<stdio.h> void fun3(void) { printf("我是函数3\n"); } //fun1.h中代码 void fun1(void);//声明函数 //fun2.h中代码 void fun2(void);//声明函数 //fun3.h中代码 void fun3(void);//声明函数
以下是多文件函数运行机制在内存中的运行过程
重要说明:
1.在单文件编程中,所有的函数都定义在一起,这样就显得程序杂乱无章,如果程序中就几个函数的话,写在一起感觉还可以,如果一个庞大的工程涉及10000个函数呢?是不是感觉放在一起会很乱啊,所以就引出多文件模块化编程。
2.在单文件编程时,如果被调用函数定义在调用函数之前的话,那么就不用声明,如果被调用函数定义在调用函数之后的话,就需要在前边声明,否则编译器会报错误。那多文件编程时,函数定义在另一个.c文件中,如果其它.c文件要调用它时,就必须要声明,目的就是告诉编译器,我要调用它,你别报错啊,同样,编译器看到你声明了以后,就知道你在本文件或者其它.c文件中定义了该函数,就不会报错了。
3.fun1.c和fun1.h除了后缀以外名称相同,那它俩有什么关系呢?因为.h文件中一般只放函数声明、宏定义、变量声明,而函数的具体实现是定义在.c文件中的,名字相同就是.c文件用到的一些定义(除了函数)会定义在同名的.h文件中,更加方便维护。它俩名称可以不相同。
4.头文件中一般放置函数原型(声明)和宏定义,有时也可放置结构体声明、静态全局变量的定义。
5.<>和""的区别:
<stdio.h>:尖括号包含文件,将告诉预处理器在“编译器自带”的头文件中搜索stdio.h;
“stdio.h”:双引号包含文件,将告诉预处理器在“当前程序所在文件夹”搜索stdio.h,如果没有,再去编译器自带头文件中搜索。 -
C语言函数详解
2022-06-01 21:48:26C语言函数详解C语言函数_C语言技术网-码农有道的博客-CSDN博客_c语言函数
函数调用的过程,其实就是实参传递给形参的一个过程。这个传递实际是一次拷贝。实际参数的时候,实参(本质是一个变量)本身并没有进入到函数内,而是把自己的值复制了一份传给了函数中的形参,在函数中参与运算。这种传参方法,就叫做传值调用。
C语言函数调用三种方式:传值调用,引用调用和传地址调用_yuyaweibest的博客-CSDN博客_传值调用
C语言中函数调用中的传值与传址_ljx_csdn的博客-CSDN博客_函数传值和传地址
总结:函数使用的三大要素:函数定义、函数声明、函数调用
1、如果没有定义,只有声明和调用:编译时会报连接错误。undefined reference to `func_in_a'
2、如果没有声明,只有定义和调用:编译时一般会报警告,极少数情况下不会报警告。但是最好加上声明。
3、如果没有调用,只有定义和声明:编译时一般会报警告(有一个函数没有使用),有时不会报警告。这时候程序执行不会出错,只是你白白的写了几个函数,而没有使用浪费掉了而已。实验:在一个项目的两个.c文件中,分别定义一个名字相同的函数,结果?
编译报错 multiple definition of `func_in_a'
结论:在一个程序中,不管是一个文件内,还是该程序的多个文件内,都不能出现函数名重复的情况,一旦重复,编译器就会报错。主要是因为编译器不知道你调用该函数时到底调用的是哪个函数,编译器在调用函数时是根据函数名来识别不同的函数的。函数
函数的本质
C语言为什么会有函数
(1)整个程序分成多个源文件,一个文件分成多个函数,一个函数分成多个语句,这就是整个程序的组织形式。这样组织的好处在于:分化问题、便于编写程序、便于分工。
(2)函数的出现是人(程序员和架构师)的需要,而不是机器(编译器、CPU)的需要。
(3)函数的目的就是实现模块化编程。说白了就是为了提供程序的可移植性。函数书写的一般原则:
第一:遵循一定格式。函数的返回类型、函数名、参数列表等。
第二:一个函数只做一件事:函数不能太长也不宜太短,原则是一个函数只做一件事情。
第三:传参不宜过多:在ARM体系下,传参不宜超过4个。如果传参确实需要多则考虑结构体打包
第四:尽量少碰全局变量:函数最好用传参返回值来和外部交换数据,不要用全局变量。函数是动词、变量是名词(面相对象中分别叫方法和成员变量)
(1)函数将来被编译成可执行代码段,变量(主要指全局变量)经过编译后变成数据或者在运行时变成数据。一个程序的运行需要代码和数据两方向的结合才能完成。
(2)代码和数据需要彼此配合,代码是为了加工数据,数据必须借助代码来起作用。拿现实中的工厂来比喻:数据是原材料,代码是加工流水线。名词性的数据必须经过动词性的加工才能变成最终我们需要的产出的数据。这个加工的过程就是程序的执行过程。函数的实质是:数据处理器
(1)程序的主体是数据,也就是说程序运行的主要目标是生成目标数据,我们写代码也是为了目标数据。我们如何得到目标数据?必须2个因素:原材料+加工算法。原材料就是程序的输入数据,加工算法就是程序。
(2)程序的编写和运行就是为了把原数据加工成目标数据,所以程序的实质就是一个数据处理器。
(3)函数就是程序的一个缩影,函数的参数列表其实就是为了给函数输入原材料数据,函数的返回值和输出型参数就是为了向外部输出目标数据,函数的函数体里的那些代码就是加工算法。
(4)函数在静止没有执行(乖乖的躺在硬盘里)的时候就好象一台没有开动的机器,此时只占一些存储空间但是并不占用资源(CPU+内存);函数的每一次运行就好象机器的每一次开机运行,运行时需要耗费资源(CPU+内存),运行时可以对数据加工生成目标数据;函数运行完毕会释放占用的资源。
(5)整个程序的运行其实就是很多个函数相继运行的连续过程。函数三要素:定义、声明、调用
(1)函数的定义就是函数体、函数声明是函数原型、函数调用就是使用函数
(2)函数定义是函数的根本,函数定义中的函数名表示了这个函数在内存中的首地址,所以可以用函数名来调用执行这个函数(实质是指针解引用访问);函数定义中的函数体是函数的执行关键,函数将来执行时主要就是执行函数体。所以一个函数没有定义就是无基之塔。
(3)函数声明的主要作用是告诉编译器函数的原型
(4)函数调用就是调用执行一个函数。函数原型和作用
(1)函数原型就是函数的声明,说白了就是函数的函数名、返回值类型、参数列表。
(2)函数原型的主要作用就是给编译器提供原型,让编译器在编译程序时帮我们进行参数的静态类型检查
(3)必须明白:编译器在编译程序时是以单个源文件为单位的(所以一定要在哪里调用在哪里声明),而且编译器工作时已经经过预处理处理了,最最重要的是编译器编译文件时是按照文件中语句的先后顺序执行的。
(4)编译器从源文件的第一行开始编译,遇到函数声明时就会收到编译器的函数声明表中,然后继续向后。当遇到一个函数调用时,就在我的本文件的函数声明表中去查这个函数,看有没有原型相对应的一个函数(这个相对应的函数有且只能有一个)。如果没有或者只有部分匹配则会报错或报警告;如果发现多个则会报错或报警告(函数重复了,C语言中不允许2个函数原型完全一样,这个过程其实是在编译器遇到函数定义时完成的。所以函数可以重复声明但是不能重复定义)递归函数
什么是递归函数
(1)递归函数就是函数中调用了自己本身这个函数的函数。
(2)递归函数和循环的区别。递归不等于循环
(3)递归函数解决问题的典型就是:求阶乘、求斐波那契数列函数的递归调用原理
(1)实际上递归函数是在栈内存上递归执行的,每次递归执行一次就需要耗费一些栈内存。
(2)栈内存的大小是限制递归深度的重要因素。使用递归函数的原则:收敛性、栈溢出
(1)收敛性就是说:递归函数必须有一个终止递归的条件。当每次这个函数被执行时,我们判断一个条件决定是否继续递归,这个条件最终必须能够被满足。如果没有递归终止条件或者这个条件永远不能被满足,则这个递归没有收敛性,这个递归最终要失败。
(2)因为递归是占用栈内存的,每次递归调用都会消耗一些栈内存。因此必须在栈内存耗尽之前递归收敛(终止),否则就会栈溢出。
(3)递归函数的使用是有一定风险的,必须把握好。函数库
什么是函数库?
(1)函数库就是一些事先写好的函数的集合,给别人复用。
(2)函数是模块化的,因此可以被复用。我们写好了一个函数,可以被反复使用。也可以A写好了一个函数然后共享出来,当B有相同的需求时就不需自己写直接用A写好的这个函数即可。函数库的由来
(1)最开始是没有函数库,每个人写程序都要从零开始自己写。时间长了慢慢的早期的程序员就积累下来了一些有用的函数。
(2)早期的程序员经常参加行业聚会,在聚会上大家互相交换各自的函数库。
(3)后来程序员中的一些大神就提出把大家各自的函数库收拢在一起,然后经过校准和整理,最后形成了一份标准化的函数库,就是现在的标准的函数库,譬如说glibc。函数库的提供形式:动态链接库与静态链接库
动态链接库与静态链接库的区别_breakpoints_的博客-CSDN博客_动态链接库和静态链接库的区别
(1)早期的函数共享都是以源代码的形式进行的。这种方式共享是最彻底的(后来这种源码共享的方向就形成了我们现在的开源社区)。但是这种方式有它的缺点,缺点就是无法以商业化形式来发布函数库。
(2)商业公司需要将自己的有用的函数库共享给被人(当然是付费的),但是又不能给客户源代码。这时候的解决方案就是以库(主要有2种:静态库和动态库)的形式来提供。
(3)比较早出现的是静态链接库。静态库其实就是商业公司将自己的函数库源代码经过只编译不连接形成.o的目标文件,然后用ar工具将.o文件归档成.a的归档文件(.a的归档文件又叫静态链接库文件)。商业公司通过发布.a库文件和.h头文件来提供静态库给客户使用;客户拿到.a和.h文件后,通过.h头文件得知库中的库函数的原型,然后在自己的.c文件中直接调用这些库文件,在连接的时候链接器会去.a文件中拿出被调用的那个函数的编译后的.o二进制代码段链接进去形成最终的可执行程序。
(4)动态链接库比静态链接库出现的晚一些,效率更高一些,是改进型的。现在我们一般都是使用动态库。静态库在用户链接自己的可执行程序时就已经把调用的库中的函数的代码段链接进最终可执行程序中了,这样好处是可以执行,坏处是太占地方了。尤其是有多个应用程序都使用了这个库函数时,实际上在多个应用程序最后生成的可执行程序中都各自有一份这个库函数的代码段。当这些应用程序同时在内存中运行时,实际上在内存中有多个这个库函数的代码段,这完全重复了。而动态链接库本身不将库函数的代码段链接入可执行程序,只是做个标记。然后当应用程序在内存中执行时,运行时环境发现它调用了一个动态库中的库函数时,会去加载这个动态库到内存中,然后以后不管有多少个应用程序去调用这个库中的函数都会跳转到第一次加载的地方去执行(不会重复加载)。函数库中库函数的使用
(1)gcc中编译链接程序默认是使用动态库的,要想静态链接需要显式用-static来强制静态链接。
(2)库函数的使用需要注意3点:第一,包含相应的头文件;第二,调用库函数时注意函数原型;第三,有些库函数链接时需要额外用-lxxx来指定链接;第四,如果是动态库,要注意-L指定动态库的地址。字符串函数
C库中字符串处理函数包含在string.h中,这个文件在ubuntu系统中在/usr/include中
数学库函数
math.h
(1)真正的数学运算的函数定义在:/usr/include/i386-linux-gnu/bits/mathcalls.h
(2)使用数学库函数的时候,只需要包含math.h即可。……
注意区分编译时警告/错误,和链接时的错误:
编译时警告/错误:
math.c:9:13: warning: incompatible implicit declaration of built-in function ‘sqrt’ [enabled by default]double b = sqrt(a);
链接时错误:
math.c:(.text+0x1b): undefined reference to `sqrt'
collect2: error: ld returned 1 exit status分析;这个链接错误的意思是:sqrt函数有声明(声明就在math.h中)有引用(在math.c)但是没有定义,链接器找不到函数体。sqrt本来是库函数,在编译器库中是有.a和.so链接库的(函数体在链接库中的)。
C链接器的工作特点:因为库函数有很多,链接器去库函数目录搜索的时间比较久。为了提升速度想了一个折中的方案:链接器只是默认的寻找几个最常用的库,如果是一些不常用的库中的函数被调用,需要程序员在链接时明确给出要扩展查找的库的名字。链接时可以用-lxxx来指示链接器去到libxxx.so中去查找这个函数。链接时加-lm
(1)-lm就是告诉链接器到libm中去查找用到的函数。
(2)实战中发现在高版本的gcc中,经常会出现没加-lm也可以编译链接的。自己制作静态链接库并使用
(1)第一步:自己制作静态链接库
首先使用gcc -c只编译不连接,生成.o文件;然后使用ar工具进行打包成.a归档文件
库名不能随便乱起,一般是lib+库名称,后缀名是.a表示是一个归档文件
注意:制作出来了静态库之后,发布时需要发布.a文件和.h文件。(2)第二步:使用静态链接库
把.a和.h都放在我引用的文件夹下,然后在.c文件中包含库的.h,然后直接使用库函数。
第一次,编译方法:gcc test.c -o test
报错信息:test.c:(.text+0xa): undefined reference to `func1'
test.c:(.text+0x1e): undefined reference to `func2'
第二次,编译方法:gcc test.c -o test -laston
报错信息:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status
第三次,编译方法:gcc test.c -o test -laston -L.
无报错,生成test,执行正确。(3)除了ar名另外,还有个nm命令也很有用,它可以用来查看一个.a文件中都有哪些符号
自己制作动态链接库并使用
(1)动态链接库的后缀名是.so(对应windows系统中的dll),静态库的扩展名是.a
(2)第一步:创建一个动态链接库。
gcc aston.c -o aston.o -c -fPIC
gcc -o libaston.so aston.o -shared
-fPIC是位置无关码,-shared是按照共享库的方式来链接。
注意:做库的人给用库的人发布库时,发布libxxx.so和xxx.h即可。
(3)第二步:使用自己创建的共享库。
第一步,编译方法:gcc test.c -o test
报错信息:test.c:(.text+0xa): undefined reference to `func1'
test.c:(.text+0x1e): undefined reference to `func2'
collect2: error: ld returned 1 exit status第二步,编译方法:gcc test.c -o test -laston
报错信息:/usr/bin/ld: cannot find -laston
collect2: error: ld returned 1 exit status第三步,编译方法:gcc test.c -o test -laston -L.
编译成功但是运行出错,报错信息:
error while loading shared libraries: libaston.so: cannot open shared object file: No such file or directory错误原因:动态链接库运行时需要被加载(运行时环境在执行test程序的时候发现他动态链接了libaston.so,于是乎会去固定目录尝试加载libaston.so,如果加载失败则会打印以上错误信息。)
解决方法一:
将libaston.so放到固定目录下就可以了,这个固定目录一般是/usr/lib目录。
cp libaston.so /usr/lib即可解决方法二:使用环境变量LD_LIBRARY_PATH。操作系统在加载固定目录/usr/lib之前,会先去LD_LIBRARY_PATH这个环境变量所指定的目录下去寻找,如果找到就不用去/usr/lib下面找了,如果没找到再去/usr/lib下面找。所以解决方案就是将libaston.so所在的目录导出到环境变量LD_LIBRARY_PATH中即可。
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/mnt/hgfs/Winshare/s5pv210/AdvancedC/4.6.PreprocessFunction
/4.6.12.sharedobject.c/sotest
在ubuntu中还有个解决方案三,用ldconfig
(4)ldd命令:作用是可以在一个使用了共享库的程序执行之前解析出这个程序使用了哪些共享库,并且查看这些共享库是否能被找到,能被解析(决定这个程序是否能正确执行)。
main函数
main函数返回给谁?
函数为什么需要返回值
(1)函数在设计的时候设计了参数和返回值,参数是函数的输入,返回值是函数的输出。
(2)因为函数需要对外输出数据(实际上是函数运行的一些结果值)因此需要返回值
(3)形式上来说,函数被另一个函数所调用,返回值作为函数式的值返回给调用这个函数的地方
总结:函数的返回值就是给调用它的人返回一个值
main函数被谁调用
(1)main函数是特殊的,首先这个名字是特殊的。因为C语言规定了main函数是整个程序的入口。其他的函数只有直接或间接被main函数所调用才能被执行,如果没有被main直接/间接调用则这个函数在整个程序中无用。
(2)main函数从某种角度来讲代表了我当前这个程序,或者说代表了整个程序。main函数的开始意味着整个程序开始执行,main函数的结束返回意味着整个程序的结束。
(3)谁执行了这个程序,谁就调用了main。
(4)谁执行了程序?或者说程序有哪几种被调用执行的方法?linux下一个新程序执行的本质
(1)表面来看,linux中在命令行中去./xx执行一个可执行程序
(2)我们还可以通过shell脚本来调用执行一个程序
(3)我们还可以在程序中去调用执行一个程序(fork exec)
总结:我们有多种方法都可以执行一个程序,但是本质上是相同的。linux中一个新程序的执行本质上是一个进程的创建、加载、运行、消亡。linux中执行一个程序其实就是创建一个新进程然后把这个程序丢进这个进程中去执行直到结束。新进程是被谁开启?在linux中进程都是被它的父进程fork出来的。
分析:命令行本身就是一个进程,在命令行底下去./xx执行一个程序,其实这个新程序是作为命令行进程的一个字进程去执行的。
总之一句话:一个程序被它的父进程所调用。
结论:main函数返回给调用这个函数的父进程。父进程要这个返回值干嘛?父进程调用子进程来执行一个任务,然后字进程执行完后通过main函数的返回值返回给父进程一个答复。这个答复一般是表示子进程的任务执行结果完成了还是错误了。(0表示执行成功,负数表示失败)实践验证获取main的返回值
(1)用shell脚本执行程序可以获取程序的返回值并且打印出来
(2)linux shell中用$?这个符号来存储和表示上一个程序执行结果。启示
(1)任何人任何事物都是有妈生的,不会无缘无故出现或消亡。
(2)看起来没用、改掉或去掉没错的,也不见得就真的没用没错。要大胆总结更要小心求证。argc、argv与main函数的传参
谁给main函数传参
(1)调用main函数所在的程序的它的父进程给main函数传参,并且接收main的返回值。
为什么需要给main函数传参
(1)首先,main函数不传参是可以的,也就是说父进程调用子程序并且给子程序传参不是必须的。 int main(void)这种形式就表示我们认为不必要给main传参。
(2)有时候我们希望程序有一种灵活性,所以选择在执行程序时通过传参来控制程序中的运行,达到不需要重新编译程序就可以改变程序运行结果的效果。表面上:给main传参是怎样实现的?
(1)给main传参通过argc和argv这两个C语言预订的参数来实现
(2)argc是int类型,表示运行程序的时候给main函数传递了几个参数;argv是一个字符串数组,这个数组用来存储多个字符串,每个字符串就是我们给main函数传的一个参数。argv[0]就是我们给main函数的第一个传参,argv[1]就是传给main的第二个参数····本质上:给main传参是怎样实现的?
(1)上节课讲过,程序调用有各种方法但是本质上都是父进程fork一个子进程,然后字进程和一个程序绑定起来去执行(exec函数族),我们在exec的时候可以给他同时传参。
(2)程序调用时可以被传参(也就是main的传参)是操作系统层面的支持完成的。给main传参要注意什么
(1)main函数传参都是通过字符串传进去的。
(2)程序被调用时传参,各个参数之间是通过空格来间隔的。
(3)在程序内部如果要使用argv,那么一定要先检验argc。题目:写个计算器,然后运行时可以 ./calculator 3 + 5,程序执行返回8
char *argv[],这表示字符串数组,因为在c语言中,字符串用指针来表示,所以这个数组里存的是指针数组,也可以理解成字符串数组。如果是char argv[],那就表示的是字符数组。
函数参数和返回值
函数为什么需要形参与返回值
(1)函数名是一个符号,表示整个函数代码段的首地址,实质是一个指针常量,所以在程序中使用到函数名时都是当地址用的,用来调用这个函数的。
(2)函数体是函数的关键,由一对{}括起来,包含很多句代码,函数体就是函数实际做的工作。
(3)形参列表和返回值。形参是函数的输入部分,返回值是函数的输出部分。对函数最好的理解就是把函数看成是一个加工机器(程序其实就是数据加工器),形参列表就是这个机器的原材料输入端;而返回值就是机器的成品输出端。
(4)其实如果没有形参列表和返回值,函数也能对数据进行加工,用全局变量即可。用全局变量来传参和用函数参数列表返回值来传参各有特点,在实践中都有使用。总的来说,函数参数传参用的比较多,因为这样可以实现模块化编程,而C语言中也是尽量减少使用全局变量。
(5)全局变量传参最大的好处就是省略了函数传参的开销,所以效率要高一些;但是实战中用的最多的还是传参,如果参数很多传参开销非常大,通常的做法是把很多参数打包成一个结构体,然后传结构体变量指针进去。函数传参中使用const指针
(1)const一般用在函数参数列表中,用法是const int *p;(意义是指针变量p本身可变的,而p所指向的变量是不可变的)。
(2)const用来修饰指针做函数传参,作用就在于声明在函数内部不会改变这个指针所指向的内容,所以给该函数传一个不可改变的指针(char *p = "linux";这种)不会触发错误;而一个未声明为const的指针的函数,你给他传一个不可更改的指针的时候就要小心了。输出型参数
C语言通过指针参数返回值_ISMidi的博客-CSDN博客_指针作为参数如何返回
函数传参中使用const指针
(1)const一般用在函数参数列表中,用法是const int *p;(意义是指针变量p本身可变的,而p所指向的变量是不可变的)。
(2)const用来修饰指针做函数传参,作用就在于声明在函数内部不会改变这个指针所指向的内容,所以给该函数传一个不可改变的指针(char *p = "linux";这种)不会触发错误;而一个未声明为const的指针的函数,你给他传一个不可更改的指针的时候就要小心了。函数需要向外部返回多个值时怎么办?
(1)一般来说,函数的收入部分就是函数参数,输出部分就是返回值。问题是函数的参数可以有很多个,而返回值只能有1个。这就造成我们无法让一个函数返回多个值。
(2)现实编程中,一个函数需要返回多个值是非常普遍的,因此完全依赖于返回值是不靠谱的,通常的做法是用参数来做返回(在典型的linux风格函数中,返回值是不用来返回结果的,而是用来返回0或者负数用来表示程序执行结果是对还是错,是成功还是失败)。
(3)普遍做法,编程中函数的输入和输出都是靠函数参数的,返回值只是用来表示函数执行的结果是对(成功)还是错(失败)。如果这个参数是用来做输入的,就叫输入型参数;如果这个参数的目的是用来做输出的,就叫输出型参数。
(4)输出型参数就是用来让函数内部把数据输出到函数外部的。总结
看到一个函数的原型后,怎么样一眼看出来哪个参数做输入哪个做输出?函数传参如果传的是普通变量(不是指针)那肯定是输入型参数;如果传指针就有2种可能性了,为了区别,经常的做法是:如果这个参数是做输入的(通常做输入的在函数内部只需要读取这个参数而不会需要更改它)就在指针前面加const来修饰;如果函数形参是指针变量并且还没加const,那么就表示这个参数是用来做输出型参数的。
譬如C库函数中strcpy函数 -
在Linux上实现Python调用C语言函数
2021-05-21 16:41:54假设我们所需调用的c文件名为test.c,文件里有我们需要的函数func(x,y).将.c文件编译成 .so文件gcc -fPIC -shared test.c -o test.so运行后会看到有test.so文件生成。在Python中导入C文件在当前目录下打开Python... -
C语言函数调用时候内存中栈的动态变化详细分析
2019-05-02 00:23:41先了解如下几点知识和过程: ...这些指令代码是存放在内存中进程的代码段,同一个函数内的指令代码是按照地址顺序存储的(编译器决定的)(也就是说只要指令地址+1就可以自动得到下一条指令的地址... -
c语言回调函数的使用及实际作用详解
2021-07-16 23:49:20今天给大家讲一下芯片/模块厂家写SDK必须会使用的一种技术:回调函数。 回调函数这个知识点其实并不是很难,难是难在网上很多讲解回调函数的都说的太学术化了化了,一点也不亲民。 很多人即使知道怎么写回调函数也... -
C语言函数库查询系统(ACCESS)
2019-07-04 10:51:42这个主要使用ACCESS来进行制作,可以将C语言的函数、全局变量、宏、联合、结构、枚举等类型的字符串分类储存起来,能使用窗体查询这里里面的函数或者宏,也可以用来查看某个函数属于哪个头文件,比如printf()函数: ... -
7、C语言——函数
2021-05-24 04:15:14函数1、函数定义函数返回值类型 函数名(形式参数列表){函数体;}注意:定义有参函数时,形参的定义可以采用传统方式或现代方式两种1)传统方式:int max(x,y)int x,y;不能定义形参以外的其它变量{}2)现代方式:int ... -
C语言---文件操作及文件操作函数详解
2022-04-04 20:07:38从本专栏第一篇博客起到现在,我们写过的所有的程序,包括扫雷、三子棋、通讯录,它们的运行都是一次性的。当运行程序时,我们所写入和输出的内容都是存储于计算机内存中的,当程序运行结束就会消失。当重启程序,... -
C语言中的函数
2021-05-05 10:35:17在一个大的程序里,由一条或者多条语句组成的一个部分代码块,为了实现某一个特定的功能,相对于其他部分的代码,这部分代码相对独立,一般都有入参和返回值,使用者直接使用此函数就可以,不用关心内部的处理,也... -
甜甜C语言——sscanf()函数
2022-05-09 13:09:12文章目录前言函数头文件函数原型函数功能实现初级用法纯数字转换(int)纯数字转换(double)高级用法取指定长度的字符串取到指定字符为止关于 * 的使用高高级用法财大OJ例题结语 前言 最近突然多出来了C语言实践课,... -
C语言 子函数return(局部变量&栈地址)机制 笔记
2021-05-20 07:27:28/********************* 分割线 ***************************/0引C/C++中,函数内部的一切变量(函数内部局部变量,形参)都是在其被调用时才被分配内存单元。子函数运行结束时,所有局部变量的内存单元会被... -
C语言基础_递归函数
2022-03-31 10:50:08大二初学C语言遇到递归函数时,觉得很烧脑,虽然当时马马虎虎学了,但在我的印象深处总觉得递归函数是一个不大不小的高山,想翻越它需要费很大功夫,好了,废话不多说,开始整理吧! 递归函数 定义 什么是递归? ... -
c语言文件读写实例
2021-05-20 11:34:06char ch,infile[10],outfile[10文件使用方式 意义 “rt” 只读打开一个文本文件,只允许读数据 “wt” 只写打开或建立一个文本文件,只允许写数据 “at” 追加打开一个文本文件,并在文件末尾写数据 “rb” 只读... -
C语言预处理和函数
2017-05-28 15:18:10预处理是C语言中的一个重要特性,值得我们深入讨论, 源代码到可执行程序 源代码经过编译。生成.o文件,再经过链接,成为elf可执行程序,但是实际上,源码经过编译之后,不是直接的.o文件,而是汇编文件.S,汇编... -
C语言之函数与预编译= =
2021-05-18 14:01:54函数定义C语言程序框架C语言程序的框架有两种:1.一个main()单框架2.一个main()+多个子函数复合框架注意:1....函数不能嵌套定义,但可以互相调用,不能调用main()C语言函数分类函数的分类:(1)无参函数和有参函数(... -
C语言程序设计笔记
2021-05-19 13:20:18《C语言程序设计笔记》由会员分享,可在线阅读,更...其中编译是指将源程序(*,c)翻译成二进制形式的目标程序(*.obj),连接是指将目标程序与系统的函数库或用户自定义的目标程序连接起来形成可执行的目标程序(*.exe)... -
方法名同类名相同如果没有__construct,会被当做构造函数。
2021-05-23 10:24:22简介本文主要罗列些例子,看看当php类名和函数名重名时,php是如何处理的例子class TestObject{public $subject;private $message = 'test' . PHP_EOL;public function testObject(){echo $this->message;}}$... -
C语言学习笔记——函数
2019-01-15 19:35:28一、函数的框架 函数的框架: 返回类型 函数名字 (参数1,参数2,........) { 函数主体 ... return 函数返回的结果。... 可以在函数中定义函数,但是不推荐使用,会使思路非常乱 函数中定义的参数变量空... -
C语言函数
2012-03-04 18:50:56函数名 参数表 一个完整的函数应该是这样的: 函数返回值类型 函数名(参数表) { 语句体; } 函数返回值类型可以是前面说到的某个数据类型、或者是某个数据类型的指针、指向结构的指针、指向数组的指针。指针... -
C语言第七章:函数
2022-03-02 23:07:53第七章:函数 C语言程序的框架有两种...2.一个C语言程序可以由 个或多个源程序文件组成 语言程序可以由一个或多个源程序文件组成。 3.C程序执行总是从main()开始,结束于main()结束;可调 -
C语言复习:函数
2022-04-29 22:17:55函数名>(形参) {函数体} 类型的话如果用void代表 空 ,放在返回值/类型位置上,说明此函数执行完不返回任何值;放在参数位置上则显示说明无形式参数 返回值 通过return返回到主调函数里,并结束函数。 ... -
c语言中同一函数下不可以重复定义变量
2016-01-04 14:19:39main() { int i=0;...那么编译器就会报错,报错都是乱报的错误,因为在同一个函数中给同一个变量名重复定义两次,编译器则无法识别,好比两个房子安装了同样的门,那么我们则无法区别这两个房子。 -
C语言中关于函数的总结
2019-07-16 15:23:00C语言中关于函数的总结知识点敲黑板先看这里函数意义函数参数 知识点 函数意义、 函数参数、 函数设计原则、 函数与宏 敲黑板先看这里 你觉不觉得C语言基础知识概念在头脑里没有形成知识体系?是否比较模糊... -
2.6 C语言入职例程三:函数指针和程序框架入门
2020-01-31 12:27:25这也证明了函数本身就是指针了,因此函数aa的调用也可以写成如下的样子: (*aa)(1); 理解了这个概念,碰到嵌入式产品中,需要C语言和汇编混合编程时,就会少一些困惑。 实际上同函数类似的还有数组,数组也是指针,... -
C语言宏定义与预处理、函数和函数库
2021-08-09 00:07:24目录前言一、C语言预处理二、宏定义三、函数四、函数库五、自己制作静态链接库(ubuntu 环境下,即使用Linux系统平台上的gcc)六、自己制作动态链接库(ubuntu 环境下,即使用Linux系统平台上的gcc) 前言 本篇文章...