混合式编程
⑴ C和C++如何混合编程
在用C++的项目源码中,经常会不可避免的会看到下面的代码:
1
#ifdef __cplusplus
2
extern "C" {
3
#endif
4
5
/*...*/
6
7
#ifdef __cplusplus
8
}
9
#endif
它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试or笔试中。下面我就从以下几个方面来介绍它:
1、#ifdef _cplusplus/#endif _cplusplus及发散
2、extern "C"
2.1、extern关键字
2.2、"C"
2.3、小结extern "C"
3、C和C++互相调用 4、C和C++混合调用特别之处函数指针
3.1、C++的编译和连接
3.2、C的编译和连接
3.3、C++中调用C的代码
3.4、C中调用C++的代码
1、#ifdef _cplusplus/#endif _cplusplus及发散
在介绍extern "C"之前,我们来看下#ifdef
_cplusplus/#endif
_cplusplus的作用。很明显#ifdef/#endif、#ifndef/#endif用于条件编译,#ifdef
_cplusplus/#endif
_cplusplus——表示如果定义了宏_cplusplus,就执行#ifdef/#endif之间的语句,否则就不执行。
在这里为什么需要#ifdef _cplusplus/#endif
_cplusplus呢?因为c语言中不支持extern "C"声明,如果你明白extern
"C"的作用就知道在C中也没有必要这样做,这就是条件编译的作用!在.c文件中包含了extern "C"时会出现编译时错误。
既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。还记得腾讯笔试就考过这个题目,给出类似下面的代码(下面是我最近在研究的一个开源web服务器——Mongoose的头文件mongoose.h中的一段代码):
01
#ifndef MONGOOSE_HEADER_INCLUDED
02
#define MONGOOSE_HEADER_INCLUDED
03
04
#ifdef __cplusplus
05
extern "C" {
06
#endif /* __cplusplus */
07
08
/*.................................
09
* do something here
10
*.................................
11
*/
12
13
#ifdef __cplusplus
14
}
15
#endif /* __cplusplus */
16
17
#endif /* MONGOOSE_HEADER_INCLUDED */
然后叫你说明上面宏#ifndef/#endif的作用?为了解释一个问题,我们先来看两个事实:
这个头文件mongoose.h可能在项目中被多个源文件包含(#include
"mongoose.h"),而对于一个大型项目来说,这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被#include两次(如,a.h头文件包含了mongoose.h,而在b.c文件中#include
a.h和mongoose.h)——这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立。
为了解决这个问题,上面代码中的
#ifndef MONGOOSE_HEADER_INCLUDED
#define MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#endif /* MONGOOSE_HEADER_INCLUDED */
就起作用了。如果定义了MONGOOSE_HEADER_INCLUDED,#ifndef/#endif之间的内容就被忽略掉。因此,编译时第一次看到mongoose.h头文件,它的内容会被读取且给定MONGOOSE_HEADER_INCLUDED一个值。之后再次看到mongoose.h头文件时,MONGOOSE_HEADER_INCLUDED就已经定义了,mongoose.h的内容就不会再次被读取了。
2、extern "C"
首先从字面上分析extern "C",它由两部分组成——extern关键字、"C"。下面我就从这两个方面来解读extern "C"的含义。
2.1、extern关键字
在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。首先来一个例子:
1
//file1.c:
2
int x=1;
3
int f(){do something here}
4
//file2.c:
5
extern int x;
6
int f();
7
void g(){x=f();}
在file2.c中g()使用的x和f()是定义在file1.c中的。extern关键字表明file2.c中x,仅仅是一个变量的声明,其并不是在定义变量x,并未为x分配内存空间。变量x在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。但是可以声明多次,且声明必须保证类型一致,如:
1
//file1.c:
2
int x=1;
3
int b=1;
4
extern c;
5
//file2.c:
6
int x;// x equals to default of int type 0
7
int f();
8
extern double b;
9
extern int c;
在这段代码中存在着这样的三个错误:
x被定义了两次
b两次被声明为不同的类型
c被声明了两次,但却没有定义
回到extern关键字,extern是C/C++语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。例如,如果模块B欲引用该模块A中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块B中调用模块A中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与extern对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。
2.2、"C"
典型的,一个C++程序包含其它语言编写的部分代码。类似的,C++编写的代码片段可能被使用在其它语言编写的代码中。不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。
为了使它们遵守统一规则,可以使用extern指定一个编译和连接规约。例如,声明C和C++标准库函数strcyp(),并指定它应该根据C的编译和连接规约来链接:
1
extern "C" char* strcpy(char*,const char*);
注意它与下面的声明的不同之处:
1
extern char* strcpy(char*,const char*);
下面的这个声明仅表示在连接的时候调用strcpy()。
extern "C"指令非常有用,因为C和C++的近亲关系。注意:extern "C"指令中的C,表示的一种编译和连接规约,而不是一种语言。C表示符合C语言的编译和连接规约的任何语言,如Fortran、assembler等。
还有要说明的是,extern "C"指令仅指定编译和连接规约,但不影响语义。例如在函数声明中,指定了extern "C",仍然要遵守C++的类型检测、参数转换规则。
再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定extern关键字,但是当你又加上了"C",它不会改变语义,但是会改变它的编译和连接方式。
如果你有很多语言要加上extern "C",你可以将它们放到extern "C"{ }中。
2.3、小结extern "C"
通过上面两节的分析,我们知道extern "C"的真实目的是实现类C和C++的混合编程。在C++源文件中的语句前面加上extern "C",表明它按照类C的编译和连接规约来编译和连接,而不是C++的编译的连接规约。这样在类C的代码中就可以调用C++的函数or变量等。(注:我在这里所说的类C,代表的是跟C语言的编译和连接方式一致的所有语言)
3、C和C++互相调用
我们既然知道extern "C"是实现的类C和C++的混合编程。下面我们就分别介绍如何在C++中调用C的代码、C中调用C++的代码。首先要明白C和C++互相调用,你得知道它们之间的编译和连接差异,及如何利用extern "C"来实现相互调用。
3.1、C++的编译和连接
C++是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:
1
void print(int i);
2
void print(char c);
3
void print(float f);
4
void print(char* s);
编译为:
1
_print_int
2
_print_char
3
_print_float
4
_pirnt_string
这样的函数名,来唯一标识每个函数。注:不同的编译器实现可能不一样,但是都是利用这种机制。所以当连接是调用print(3)时,它会去查找_print_int(3)这样的函数。下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。
C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。
3.2、C的编译和连接
C语言中并没有重载和类这些特性,故并不像C++那样print(int
i),会被编译为_print_int,而是直接编译为_print等。因此如果直接在C++中调用C的函数会失败,因为连接是调用C中的print(3)时,它会去找_print_int(3)。因此extern
"C"的作用就体现出来了。
3.3、C++中调用C的代码
假设一个C的头文件cHeader.h中包含一个函数print(int i),为了在C++中能够调用它,必须要加上extern关键字(原因在extern关键字那节已经介绍)。它的代码如下:
1
#ifndef C_HEADER
2
#define C_HEADER
3
4
extern void print(int i);
5
6
#endif C_HEADER
相对应的实现文件为cHeader.c的代码为:
1
#include <stdio.h>
2
#include "cHeader.h"
3
void print(int i)
4
{
5
printf("cHeader %d\n",i);
6
}
现在C++的代码文件C++.cpp中引用C中的print(int i)函数:
1
extern "C"{
2
#include "cHeader.h"
3
}
4
5
int main(int argc,char** argv)
6
{
7
print(3);
8
return 0;
9
}
执行程序输出:
3.4、C中调用C++的代码
现在换成在C中调用C++的代码,这与在C++中调用C的代码有所不同。如下在cppHeader.h头文件中定义了下面的代码:
1
#ifndef CPP_HEADER
2
#define CPP_HEADER
3
4
extern "C" void print(int i);
5
6
#endif CPP_HEADER
相应的实现文件cppHeader.cpp文件中代码如下:
1
#include "cppHeader.h"
2
3
#include <iostream>
4
using namespace std;
5
void print(int i)
6
{
7
cout<<"cppHeader "<<i<<endl;
8
}
在C的代码文件c.c中调用print函数:
1
extern void print(int i);
2
int main(int argc,char** argv)
3
{
4
print(3);
5
return 0;
6
}
注意在C的代码文件中直接#include "cppHeader.h"头文件,编译出错。而且如果不加extern int print(int i)编译也会出错。
4、C和C++混合调用特别之处函数指针
当我们C和C++混合编程时,有时候会用一种语言定义函数指针,而在应用中将函数指针指向另一中语言定义的函数。如果C和C++共享同一中编译和连接、函数调用机制,这样做是可以的。然而,这样的通用机制,通常不然假定它存在,因此我们必须小心地确保函数以期望的方式调用。
而且当指定一个函数指针的编译和连接方式时,函数的所有类型,包括函数名、函数引入的变量也按照指定的方式编译和连接。如下例:
01
typedef int (*FT) (const void* ,const void*);//style of C++
02
03
extern "C"{
04
typedef int (*CFT) (const void*,const void*);//style of C
05
void qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
06
}
07
08
void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
09
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
10
11
//style of C
12
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);
13
14
int compare(const void*,const void*);//style of C++
15
extern "C" ccomp(const void*,const void*);//style of C
16
17
void f(char* v,int sz)
18
{
19
//error,as qsort is style of C
20
//but compare is style of C++
21
qsort(v,sz,1,&compare);
22
qsort(v,sz,1,&ccomp);//ok
23
24
isort(v,sz,1,&compare);//ok
25
//error,as isort is style of C++
26
//but ccomp is style of C
27
isort(v,sz,1,&ccopm);
28
}
注意:typedef int (*FT) (const void* ,const void*),表示定义了一个函数指针的别名FT,这种函数指针指向的函数有这样的特征:返回值为int型、有两个参数,参数类型可以为任意类型的指针(因为为void*)。
最典型的函数指针的别名的例子是,信号处理函数signal,它的定义如下:
1
typedef void (*HANDLER)(int);
2
HANDLER signal(int ,HANDLER);
上面的代码定义了信函处理函数signal,它的返回值类型为HANDLER,有两个参数分别为int、HANDLER。 这样避免了要这样定义signal函数:
1
void (*signal (int ,void(*)(int) ))(int)
比较之后可以明显的体会到typedef的好处。
⑵ 单片机C语言与汇编语言混合编程有哪几种方式通常采用什么方式
C语言具简洁、灵活的特点以及丰富的库函数和功能强大的调试手段,适用面非常广泛。但在实际应用中,为了完成某种特定的功能,或需要缩短程序的运行时间,或需要对硬件进行直接操作,或需要利用操作系统的某些功能模块,这时就会使用到汇编语言程序,即要采用C语言与汇编语言的混合编程技术实现。通过研究与分析Turbo
C与汇编语言的混合编程方法,为C语言应用开发人员提供方便。
⑶ c语言和汇编语言的混合编程方法主要有哪些
C语言在实际应用中,为了完成某种特定的功能,或需要缩短程序的运行时间,或需要对硬件进行直接操作,或需要利用操作系统的某些功能模块,这时就会使用到汇编语言程序,即要采用C语言与汇编语言的混合编程技术实现。
C与汇编语言的混合编程方法,为C语言应用开发人员提供方便。汇编语言开发的程序代码短、执行速度快,而C语言是应用广泛的面向过程的开发语言。通过给出混合编程的方法、约定规则以及实现的具体步骤,说明了混合编程需要注意的问题,最后给出了相应的实例。
(3)混合式编程扩展阅读:
汇编语言具有更高的机器相关性,更加便于记忆和书写,但又同时保留了机器语言高速度和高效率的特点。汇编语言仍是面向机器的语言,很难从其代码上理解程序设计意图,设计出来的程序不易被移植,故不像其他大多数的高级计算机语言一样被广泛应用。所以在高级语言高度发展的今天,它通常被用在底层,通常是程序优化或硬件操作的场合。
⑷ 如何理解高级语言和汇编语言混合编程的
混合语言编程有:(1)整体模式;(2)非整体模式。
(1)整体模式:主程序为x语言,子程序或函数中的一部分或全部为y语言,最后通过链接程序封装在一起形成一个exe文件。
(2)非整体模式:主程序为x语言,部分程序为y语言,主、子程序均独立的生成exe文件,但在主程序中可调用其它的exe文件。参数传递通过命令行参数、返回码 或者数据文件。这种方式的混合编程相对就简单一些!
⑸ 多语言混合编程
起始所有程序都是一样的,最终都是机器码,只是在编写时使用的语言不同(编程语言都是程序的抽象,为了编程方便,本身并不是可运行的程序,最终必须编译成机器码才能运行,不同语言只是不同人编的,用的语法不同),你写的源文件无论是任何语言写的,最终编译的结果都是机器码。 函数调用在机器码就是很简单的一步,将参数表要求的参数值放入堆栈(顺序有约定),然后使用 jmp 指令跳转到要调用的函数的函数地址即可,别的程序不管你怎么实现,只要你告诉我参数表,和函数地址就能调用,所有程序最终都是机器码,自然也符合这个规则。。。 所以实现互调,只要将对方的接口程序文件映射到自己的进程中,然后预先知道他的参数表,得到他的接口函数地址,将参数放入堆栈,然后跳转到函数地址, 这样你就实现调用。。。。 winodws提供了一个文件类型叫做.dll 动态链接库,如果你希望让其他程序调用,只用将文件编译成dll即可,他就是一个程序文件,纯机器码,可以让任何程序调用。。。。
具体实现方法,通过loadliabroray("xxxxx\xxx.dll"),将该文件映射到进程,getprocaddress("函数名"),得到他的函数地址,假设你已经知道这个函数格式为 int func(int,int,char),你就定义一个该类型的函数指针 ,将getprocaddress("函数名")的地址给这个函数指针,按C语言格式调用即可,很简单的,这些都是操作系统的功劳,如果你学学windows系统编程,就知道了。。。
还有一种方式是,把另一个程序当做一个服务器程序,通过约定的数据来间接调用。。。。比如有个字符串,叫“吃饭”,你只要将这个“吃饭”这两个字发给另一个程序(操作系统提供了一个叫管道的内核对象,通过它,你可以给也使用该管道的程序发数据),他实现编了程序知道这是要干什么,然后就自己执行。。。
⑹ C51语言的混合编程
C51编译器能对C语言源程序进行高效率的编译,生成高效简洁的代码,在绝大多数场合采用C语言编程即可完成预期的目的。但有时为了编程直观或某些特殊地址的处理,还须采用一定的汇编语言编程。而在另一些场合,出于某种目的,汇编语言也可调用C语言。在这种混合编程中,关键是参数的传递和函数的返回值。它们必须有完整的约定,否则数据的交换就可能出错,例 C语言程序与汇编语言程序的调用,其子程序如下:
PUBLIC AD ;入口地址
SEG_AD SEGMENT CODE;程序段
RSEG SEG_AD
USING 0
AD: MOV R6,#00
MOV R7,#00
SETB P1.1
ACALL DELAY
CLR P1.1
ACALL DELAY
MOV R0,#10
RR0: SETB P1.2
NOP
CLR P1.2
DJNZ R0,RR0
ACALL DELAY
MOV 30H,R6 ;A/D转换的高
;两位保存在R6中
ACALL CIR
MOV R6,30H
SETB P1.2
NOP
CLR P1.2
MOV 30H,R6
ACALL CIR
MOV R6,30H
MOV R0,#8 ;A/D转换的低
;8位保存在R7中
RR2: SETB P1.2
NOP
CLR P1.2
MOV 30H,R7
ACALL CIR
MOV R7,30H
DJNZ R0,RR2
RET
CIR: CLR C
MOV C,P1.0
MOV A,30H
RLC A
MOV 30H,A
RET
END
在以上程序中,函数的返回值为一无符号整型数,根据调用规则,返回值的高位必须在R6中,低位在R7中,这样才可保证数据的传递不出错。另外,在调用过程中,必须注意寄存器的入栈。这样在以后用到A/D转换时,在C语言中调用汇编语言子程序AD()即可。
⑺ 数控机床混合编程
那是思维模式不一样, 我学数控机床,机床运动从来不爱看刀具,就看导轨运动,并且以相对测量方式对刀时想是正是负的. 就是习惯问题,无论如何做对就可以了.
⑻ HTML5混合式开发究竟有哪些优势呢
一、 四个月就拥有两年以上的项目经验
采用全真商业项目教学,学习同时积攒经验,学习中参与名企项目开发设计,模拟公司项目流程,以实现知名网站/APP前端为检验学习成果的标准,积累大牛级项目经验!根据产品需求,分析并给出完美的页面前端结构解决方案。
二、了解UI/Android/iOS,和工程师高效沟通
学习中,自带项目实训属性,Web前端开发学员可与UI、Android和iOS开发人员进行沟通,一起进行实战演练,分组做项目,价值和收获倍增!与设计师和移动开发工程师配合,利用HTML5/CSS3/JavaScript等Web技术进行产品的界面开发;了解APP制作项目全流程,掌控前端页面设计趋势,制作的页面符合新技术和审美要求。
三、 PC+移动端+混合应用,学会轻盈高效的代码
培训学习中,有行业大牛教小清新的代码,简短精悍又高效,出错率低。学习移动网站和PC网站的前端构建、交互设计与实现;学习各种类型网页前端页面开发制作和维护;学习良好的代码习惯,要求结构清晰,命名规范,逻辑性强,代码冗余率低。会这招,老板就要定了!
四、 全面掌握页面兼容性和优化
在培训学习,教高效解决各种平台浏览器的兼容性问题。包括在各种手机分辨率、PAD平台上的兼容和性能优化。学习优化前端体验和页面响应速度,优化代码并保持良好兼容性,提升Web界面的友好和易用。跨浏览器开发经验,具备独立开发JavaScript经验,清楚地了解不同浏览器上的兼容情况、渲染原理和存在的Bug,确保网站能在IE、Firefox、360、Android、iOS手机浏览器等主流浏览器正常浏览。
五、 课程方向覆盖企业前端技术岗位需求
如今,武汉前端开发工程师的职责,不是只有切图、制作网页这么简单,学习课程必须完全覆盖武汉行业内岗位需求,才能轻松胜任武汉WEB前端/移动端/混合应用开发工作。紧跟技术更新节奏,了解企业需求,不断更新课程大纲。工作用什么,就有什么。高度专注,深度优化,注重培养学员编程思维能力,起步目标锁定中高级工程师,起薪8000-12000!
⑼ 什么是数控机床的混合编程
答:简单的说就是绝对编程与增量编程一起编。 全面的如下: 数控机床编程时,可采用绝对值编程、增量值编程或二者混合编程。
1、绝对值编程
绝对值编程是根据预先设定的编程原点计算出绝对值坐标尺寸进行编程的一种方法。即采用绝对值编程时,首先要指出编程原点的位置,并用地址x、Z进行编程(X为直径值)。有的数控系统用G90指令指定绝对值编程。
2、增量值编程
增量值编程是根据与前一个位置的坐标值增量来表示位置的一种编程方法。即程序中的终点坐标是相对于起点坐标而言的。采用增量值编程时,用地址U、W代替X、Z进行编程。
3、混合编程
绝对值编程与增量值编程混合起来进行编程的方法叫混合编程。编程时也必须先设定编程原点。
⑽ python与c++混合编程如何入门
派桑和c/c++实际上是不同的工具,一个是斧头,一个是菜刀,各有各的用途。非要把让菜刀能砍柴,让斧头能切菜,这种努力有无必要,值得考虑。
派桑常用剧本,噜苏,速度慢,不严谨,但有它的便利,例如它有 list, tuple, 之类的东西。
混合编程 常用于 提高 派桑 速度。
常用 方法 是(1)包含 Python.h 文件头 (2)定义 派桑 的 各种对象 为 各种 c 结构
(3)派桑 的 各种对象用指针,名字用 Py 或 _Py大头 动态分配 在 heap (4)用函数跟踪统计 各种对象结构 的个数变化,即时 释放内存(5)及时处理 派桑 exception
(6) 输出为派桑的数据格式。
我没有合起来用过,不过基本套路如此。