Linux下C++编程基础

一、前提

  1. 以下环境均采用VMWare虚拟机安装CentOS6.6环境下编程。
  2. 想要在Linux上进行编辑,必须学会Linux基本编辑命令和其他基本命令
    学习路径:Linux系统基础一
  3. 有编程基础。

二、安装编译器,并开始第一程序

1.安装编译器gcc & g++

yum install gcc //安装gcc 
yum install gcc-c++  //安装g++

2.编写第一个程序

[root@bogon 2018-11-08]# pwd
/studySourceCode/2018-11-08
[root@bogon 2018-11-08]# vi main.cpp 
#include 
using namespace std;
int main(){
  cout << "Hello World" << endl;
  return 0;
}
[root@bogon 2018-11-08]# g++ main.cpp 
[root@bogon 2018-11-08]# ls -a
.  ..  a.out  main.cpp
[root@bogon 2018-11-08]# ./a.out 
Hello World

编写程序的方式跟在平时使用windows上的qt之类的一样。
编译程序,使用命令

g++ 文件名 

会在当前目录自动生成一个a.out的可执行文件,
执行程序,通过命令

./a.out

假如我们不想使用系统自动生成a.out的可执行文件,
可以通过使用-o选项,如下:

[root@bogon 2018-11-08]# g++ main.cpp -o HelloWorld
[root@bogon 2018-11-08]# ls -la
总用量 28
drwxr-xr-x. 2 root root 4096 11月  9 05:15 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rwxr-xr-x. 1 root root 6270 11月  9 05:10 a.out
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rw-r--r--. 1 root root  102 11月  9 05:10 main.cpp

可以看到使用选项

g++ -o 可执行程序文件名

来指定自己想要生成的可执行程序的别名。

三、C与C++混合编程

首先我们在第二小节的基础上再次增加一个c文件程序
假如是a.h

[root@bogon 2018-11-08]# vi a.h
#ifndef _A_H_
#define _A_H_
extern "C" {
        void testC();
}
#endif

编写实现,a.c

[root@bogon 2018-11-08]# vi a.c
#include "a.h"
#include 

void testC(){
  printf("I'm C language program\n");
}

main.cpp中调用testC()函数

#include 
#include "a.h"

using namespace std;
int main(){
  testC();
  cout << "Hello World" << endl;
  return 0;
}

最后再次使用g++编译程序

[root@bogon 2018-11-08]# g++ main.cpp
/tmp/cczY1jqD.o: In function `main':
main.cpp:(.text+0xa): undefined reference to `testC'
collect2: ld 返回 1

可以看到再次编译显示错误testC没有定义,那是为什么?
因为使用了a.c中的函数,但是a.c没有编译,那么只要使用g++命令将a.c编译一次,如下:

[root@bogon 2018-11-08]# g++ a.c main.cpp -o HelloWorld2
[root@bogon 2018-11-08]# ls -la
总用量 36
drwxr-xr-x. 2 root root 4096 11月  9 05:32 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   65 11月  9 05:20 a.h
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
[root@bogon 2018-11-08]# ./HelloWorld2
I'm C language program
Hello World

最后可以看到成功打印信息。

四、编译

学过编程的都知道,程序的执行顺序是:编译->链接->运行
那么g++当然也有这个操作,使用选项-c,如下:

g++ -c 文件名

实例:

[root@bogon 2018-11-08]# ls -la
总用量 36
drwxr-xr-x. 2 root root 4096 11月  9 05:44 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   46 11月  9 05:42 a.h
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
[root@bogon 2018-11-08]# g++ -c a.c -o a.o
[root@bogon 2018-11-08]# g++ -c main.cpp -i main.o
[root@bogon 2018-11-08]# ls -la
总用量 44
drwxr-xr-x. 2 root root 4096 11月  9 05:45 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   46 11月  9 05:42 a.h
-rw-r--r--. 1 root root 1088 11月  9 05:45 a.o
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
-rw-r--r--. 1 root root 1964 11月  9 05:45 main.o
[root@bogon 2018-11-08]# g++ main.o a.o -o HelloWorld3
[root@bogon 2018-11-08]# ldd HelloWorld3
        linux-gate.so.1 =>  (0x00cf9000)
        libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x0070c000)
        libm.so.6 => /lib/libm.so.6 (0x004a9000)
        libgcc_s.so.1 => /lib/libgcc_s.so.1 (0x006ec000)
        libc.so.6 => /lib/libc.so.6 (0x0
[root@bogon 2018-11-08]# ./HelloWorld3
I'm C language program
Hello World

很简单,
通过g++命令的-c选项接文件名的形式将程序进行编译
接着通过g++ 接编译后的*.o文件进行链接,生成可执行程序
通过

ldd 可执行程序

可以看到该可执行程序所链接的库

最后通过./程序名进行运行程序

五、编写makefile

从前面可以知道通过命令
g++ -c *.o即可编译程序
g++ *.o既可链接文件,生成可执行程序
那么当我们的程序非常庞大的时候,务必.cpp、.c文件时如此之多,那么我们还是通过一个一个g++ 去敲文件名的形式去编译链接吗?这想必是非常麻烦的,也不符合我们程序员的作风,那么这个时候便引入了makefile,通过该脚本,我们便可以轻松的管理我们的程序文件,那么就让我们看看怎么编写makefile吧!

我们接着第四小节的程序,首先我们去编写一个makefile吧:

[root@bogon 2018-11-08]# rm -rf *.o
[root@bogon 2018-11-08]# ls -la
总用量 56
drwxr-xr-x. 2 root root 4096 11月  9 06:02 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   46 11月  9 05:42 a.h
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rwxr-xr-x. 1 root root 6480 11月  9 05:46 HelloWorld3
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
[root@bogon 2018-11-08]# vi makefile
start:
        g++ -o main.o -c main.cpp
        g++ -o a.o -c a.c
        g++ -o HelloWorld4 a.o main.o

可以看到上面我们已经编写了一个makefile,其中

  1. makefile为脚本的名字
  2. start可以随便命名, 这里写为start表示是程序的开始部分
  3. start后面接的:,表示start为可以执行的部分,可通过命令make start执行
  4. g++命令前面的空白部分,注意不是空格,必须是键盘左上角部分的tab键

最后编译makefile脚本,使用make命令,如下:

[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o a.o -c a.c
g++ -o HelloWorld4 a.o main.o
[root@bogon 2018-11-08]# make start
g++ -o main.o -c main.cpp
g++ -o a.o -c a.c
g++ -o HelloWorld4 a.o main.o

因为start:为该makefile脚本的第一个可执行部分脚本代码,所以直接通过make也可执行start:所示部分

可以看到通过makefile脚本编译链接后的*.o文件是没有作用的了,那么我们是否有办法删除呢?
如下:

[root@bogon 2018-11-08]# vi makefile 
start:
        g++ -o main.o -c main.cpp
        g++ -o a.o -c a.c
        g++ -o HelloWorld4 a.o main.o
clean:
        rm -rf a.o main.o

可以看到makefile中是完全兼容linux命令的,所以只要再添一个clean:部分,即可通过make clean执行,如下

[root@bogon 2018-11-08]# ls -la
总用量 64
drwxr-xr-x. 2 root root 4096 11月  9 06:18 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   46 11月  9 05:42 a.h
-rw-r--r--. 1 root root 1088 11月  9 06:09 a.o
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rwxr-xr-x. 1 root root 6480 11月  9 05:46 HelloWorld3
-rwxr-xr-x. 1 root root 6480 11月  9 06:09 HelloWorld4
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
-rw-r--r--. 1 root root 1964 11月  9 06:09 main.o
-rw-r--r--. 1 root root  110 11月  9 06:16 makefile
[root@bogon 2018-11-08]# make clean
rm -rf a.o main.o
[root@bogon 2018-11-08]# ls -la
总用量 56
drwxr-xr-x. 2 root root 4096 11月  9 06:18 .
drwxr-xr-x. 4 root root 4096 11月  9 05:07 ..
-rw-r--r--. 1 root root   90 11月  9 05:24 a.c
-rw-r--r--. 1 root root   46 11月  9 05:42 a.h
-rwxr-xr-x. 1 root root 6270 11月  9 05:15 HelloWorld
-rwxr-xr-x. 1 root root 6476 11月  9 05:32 HelloWorld2
-rwxr-xr-x. 1 root root 6480 11月  9 05:46 HelloWorld3
-rwxr-xr-x. 1 root root 6480 11月  9 06:09 HelloWorld4
-rw-r--r--. 1 root root  127 11月  9 05:23 main.cpp
-rw-r--r--. 1 root root  110 11月  9 06:16 makefile

如果我们仅仅知道如上makefile的编写的话,那么别人一看肯定知道我们是菜鸟,那么怎么对上面的makefile进行优化呢?
如下:

[root@bogon 2018-11-08]# vi makefile 
XX=g++

start:
        $(XX) -o main.o -c main.cpp
        $(XX) -o a.o -c a.c
        $(XX) -o HelloWorld4 a.o main.o
clean:
        rm -rf a.o main.o
[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o a.o -c a.c
g++ -o HelloWorld4 a.o main.o  

定义方式:

变量别名=变量值

使用方式:

$(变量别名)

类似于一个简单的赋值操作,也非常类似c和c++中的宏定义去定义一个全局的变量

好处:
当有相同的变量值(文件名,命令等)重复时,可能我们需要批量去管理该变量,那么这时候只需要定义一个全局的变量,以助于我们方便管理该变量,
如我们上述所示的g++我们以后可能会替换成gcc等,那么使用之前的形式,便需要一个个需修改,通过如上述所示变可以直接修改XX=右边的值即可。是不是方便很多。

写到这里,懂的人看了其实还是认为你是个菜鸟,那么是否可以继续优化呢?接着看到:

[root@bogon 2018-11-08]# vi makefile
X=g++

start:main.o a.o
        $(XX) -o HelloWorld4 a.o main.o

a.o:
        $(XX) -o a.o -c a.c

main.o:
        $(XX) -o main.o -c main.cpp

clean:
        rm -rf a.o main.o   

可以看到我们修改的是:

  1. start:main.o a.o
    那这个代表什么意思呢?
    这个表示在start:部分执行之前,先去查找main.oa.o是否存在,不存在则去执行
a.o:
        $(XX) -o a.o -c a.c

main.o:
        $(XX) -o main.o -c main.cpp

部分,使其先生成a.o &main.o,如果存在则直接进行链接,

[root@bogon 2018-11-08]# make
g++ -o HelloWorld4 a.o main.o
[root@bogon 2018-11-08]# make clean
rm -rf a.o main.o
[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o a.o -c a.c
g++ -o HelloWorld4 a.o main.o

通过上述执行make也可看到确实如此,那么这样带来的好处又是什么呢?
我们接着修改下,a.c吧,如下:

[root@bogon 2018-11-08]# vi a.c
#include "a.h"
#include 

void testC(){
  printf("I'm C language program\n");  
  printf("change!!!!\n");
}
[root@bogon 2018-11-08]# rm a.o
rm:是否删除普通文件 "a.o"?y
[root@bogon 2018-11-08]# make
g++ -o a.o -c a.c
g++ -o HelloWorld4 a.o main.o

通过上面分析,可以清晰的看到好处就是:

  1. 当已经编译生成了.o文件,则不会再进行编译,会直接进行链接。
  2. 当某个文件进行了修改,只会再次编译该缺少的文件
    这样当一个项目非常大的时候,这是非常节省编译时间的,不需要去编译重复的文件。

当写到这里,其实这个makefile还只是很普通的,那么我们继续优化吧:

[root@bogon 2018-11-08]# mv a.c a.cpp
XX=g++

start:main.o a.o
        $(XX) -o HelloWorld5 main.o a.o

.cpp.o:
        $(XX) -o $@ -c $<

clean:
        rm -rf a.o main.o

可以看到上面我们将之前的两部分编译部分合成了一句,通过:

  1. $<表示编译的以.cpp结尾的源文件,所以我们上面首先通过mv命令将a.c重命名为a.cpp方便演示,
  2. $@表示将编译的结果重命名为.o文件

接着我们看下make效果

[root@bogon 2018-11-08]# make
g++ -o HelloWorld5 main.o a.o
[root@bogon 2018-11-08]# make clean
rm -rf a.o main.o
[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o a.o -c a.cpp
g++ -o HelloWorld5 main.o a.o
[root@bogon 2018-11-08]# vi a.cpp
#include "a.h"
#include 

void testC(){
  printf("I'm C language program\n");
  printf("change!!!!\n");
  printf("Change!!!again\n");
}
[root@bogon 2018-11-08]# make
g++ -o a.o -c a.cpp
g++ -o HelloWorld5 main.o a.o
[root@bogon 2018-11-08]# ./HelloWorld5 
I'm C language program
change!!!!
Change!!!again
Hello World

通过上面分析,可以清晰的看到好处就是:

  1. 当已经编译生成了.o文件,则不会再进行编译,会直接进行链接。
  2. 语句更加简便,方便管理
  3. 当某个文件进行了修改,只会再次编译修改的文件
    这样当一个项目非常大的时候,这是非常节省编译时间的,不需要去编译重复的文件。

到了这里makefile已经是一个比较合格的了,但是这里还是不够的,我们接着优化:

[root@bogon 2018-11-08]# vi makefile 
XX=g++
SRCS=main.cpp\
        a.cpp

OBJS=$(SRCS:.cpp=.o)

EXEC=HellWorld6

start:$(OBJS)
        $(XX) -o$(EXEC) $(OBJS)

.cpp.o:
        $(XX) -o $@ -c $<

clean:
        rm -rf $(OBJS)

最后可以看到我们将所有我们可能需要批量修改的变量通过定义变量别名的方式定义在脚本中,再次声明怎么定义:通过:

变量别名=变量(文件名,命令等)

怎么使用:

$(变量别名)

最后解析下:OBJS=$(SRCS:.cpp=.o)
这个表示将所有.cpp文件前缀文件名直接复制到文件名.o,这样当我们增加了.cpp文件后就不用手动去增加.o文件。

[root@bogon 2018-11-08]# make clean
rm -rf main.o a.o
[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o a.o -c a.cpp
g++ -o HellWorld6 main.o a.o
[root@bogon 2018-11-08]# vi main.cpp 
#include 
#include "a.h"

using namespace std;
int main(){
  testC();
  cout << "Hello World!!!!!!!!!!" << endl;
  return 0;
}
[root@bogon 2018-11-08]# make
g++ -o main.o -c main.cpp
g++ -o HellWorld6 main.o a.o

可以看到上面我们最终版的makefile的好处是:

  1. 当已经编译生成了.o文件,则不会再进行编译,会直接进行链接。
  2. 语句更加简便,方便管理
  3. 当某个文件进行了修改,只会再次编译修改的文件
    这样当一个项目非常大的时候,这是非常节省编译时间的,不需要去编译重复的文件。

1.那么make是怎么知道你修改了那些文件呢?
其实它是根据.cpp与.o的最后修改时间去判断是否需要编译,当.o文件都不存在时,则判断失去意义。

2.为什么mak不关心.h文件?
因为make是不关心.h文件的,是编译关心的,是g++关心的。

[root@bogon 2018-11-08]# g++ -E a.cpp -o a1.cpp

-E 表示预编译,那么看到a1.cpp
Linux下C++编程基础_第1张图片
从图中可以看到a1.cpp是在预编译的时候包含.h中所有的内容的。

六、windows上的代码移植到linux

在学习C++的文章中,我们在第十九小节学写了windows上socket通信,那么现在我们就将这个例子直接移植到linux上。

首先通过之前用过的工具WinSCP将qt写过的例子文件拖到Linux对应目录:

[root@bogon 2018-11-16]# ls -la
总用量 20
drwxr-xr-x. 2 root root 4096 11月 17 07:34 .
drwxr-xr-x. 8 root root 4096 11月 17 07:33 ..
-rw-r--r--. 1 root root  229 9月  24 2017 main.cpp
-rw-r--r--. 1 root root 3265 11月 17 07:28 udp.cpp
-rw-r--r--. 1 root root  238 12月 17 2017 udp.h

接着跟着之前学习过的,编写一个makefile

[root@bogon 2018-11-16]# vi makefile 
XX=g++
SRCS=main.cpp\
        udp.cpp

OBJS=$(SRCS:.cpp=.o)

EXEC=MyUdp

start:$(OBJS)
        $(XX) -o$(EXEC) $(OBJS)

.cpp.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)

比较简单,直接将之前学习过的makefile改下源文件SRCS,和可执行程序名字EXEC

需要注意的是之前学习中我们编写的文件名为:udp.c,现在我们临时把它更名为udp.cpp,因为目前学习的makefile中我们仅支持一种形式的文件,也就是都是统一的.cpp或者都是统一的.c

最后make编译一下

[root@bogon 2018-11-16]# make
g++ -o main.o -c main.cpp 
g++ -o udp.o -c udp.cpp 
udp.cpp:3:22: 错误:WinSock2.h:没有那个文件或目录
udp.cpp: In function ‘int socket_send(const char*)’:
udp.cpp:10: 错误:‘DWORD’在此作用域中尚未声明
udp.cpp:10: 错误:expected ‘;’ before ‘ver’
udp.cpp:11: 错误:‘WSADATA’在此作用域中尚未声明
udp.cpp:11: 错误:expected ‘;’ before ‘wsaData’
udp.cpp:12: 错误:‘ver’在此作用域中尚未声明
udp.cpp:12: 错误:‘MAKEWORD’在此作用域中尚未声明
udp.cpp:13: 错误:‘wsaData’在此作用域中尚未声明
udp.cpp:13: 错误:‘WSAStartup’在此作用域中尚未声明
udp.cpp:19: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:19: 错误:expected ‘;’ before ‘st’
udp.cpp:20: 错误:聚合‘sockaddr_in addr’类型不完全,无法被定义
udp.cpp:22: 错误:‘AF_INET’在此作用域中尚未声明
udp.cpp:23: 错误:‘htons’在此作用域中尚未声明
udp.cpp:24: 错误:‘inet_addr’在此作用域中尚未声明
udp.cpp:39: 错误:‘st’在此作用域中尚未声明
udp.cpp:39: 错误:‘sendto’在此作用域中尚未声明
udp.cpp:41: 错误:‘st’在此作用域中尚未声明
udp.cpp:41: 错误:‘closesocket’在此作用域中尚未声明
udp.cpp:42: 错误:‘WSACleanup’在此作用域中尚未声明
udp.cpp: In function ‘int socket_receive()’:
udp.cpp:49: 错误:‘DWORD’在此作用域中尚未声明
udp.cpp:49: 错误:expected ‘;’ before ‘ver’
udp.cpp:50: 错误:‘WSADATA’在此作用域中尚未声明
udp.cpp:50: 错误:expected ‘;’ before ‘wsaData’
udp.cpp:51: 错误:‘ver’在此作用域中尚未声明
udp.cpp:51: 错误:‘MAKEWORD’在此作用域中尚未声明
udp.cpp:52: 错误:‘wsaData’在此作用域中尚未声明
udp.cpp:52: 错误:‘WSAStartup’在此作用域中尚未声明
udp.cpp:58: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:58: 错误:expected ‘;’ before ‘st’
udp.cpp:59: 错误:聚合‘sockaddr_in addr’类型不完全,无法被定义
udp.cpp:61: 错误:‘AF_INET’在此作用域中尚未声明
udp.cpp:62: 错误:‘htons’在此作用域中尚未声明
udp.cpp:63: 错误:‘INADDR_ANY’在此作用域中尚未声明
udp.cpp:63: 错误:‘htonl’在此作用域中尚未声明
udp.cpp:66: 错误:‘st’在此作用域中尚未声明
udp.cpp:66: 错误:‘bind’在此作用域中尚未声明
udp.cpp:69: 错误:聚合‘sockaddr_in sendaddr’类型不完全,无法被定义
udp.cpp:77: 错误:‘recvfrom’在此作用域中尚未声明
udp.cpp:78: 错误:‘inet_ntoa’在此作用域中尚未声明
udp.cpp:83: 错误:‘st’在此作用域中尚未声明
udp.cpp:83: 错误:‘closesocket’在此作用域中尚未声明
udp.cpp:84: 错误:‘WSACleanup’在此作用域中尚未声明
make: *** [udp.o] 错误 1

会发现有一大堆错误,但是不要紧,我们先看到之前原始的udp.cpp

#include 
#include 
#include 


#pragma comment(lib,"ws2_32.lib")
int socket_send(const char *IP)
{

    //初始化socket
    DWORD ver;
    WSADATA wsaData;
    ver = MAKEWORD(1,1);//在调用WSAStartup()要告诉window,我用什么版本的socket
    WSAStartup(ver,&wsaData);//windows要求,只要用socket,第一步,必须调用这个函数
    //初始化Socket完成
    
    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = inet_addr(IP);//"127.0.0.1" 这个IP代表自己
//    unsigned long laddr = inet_addr("192.168.6.200");
//    printf("%x\n",laddr);
//    unsigned char *p = &laddr;
//    printf("%u,%u,%u,%u\n",*(p),*(p+1),*(p+2),*(p+3));

    char buf[1024] = {0};
    size_t rc;
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        gets(buf);
        if(buf[0] == '0')
        break;
        //发送udp数据
        rc = sendto(st,buf,strlen(buf),0,(struct sockaddr *)&addr,sizeof(addr));
    }
    closesocket(st);//使用完socket要将其关闭
    WSACleanup();//释放win socket内部相关资源
    return rc;

}

int socket_receive()
{
    //初始化socket
    DWORD ver;
    WSADATA wsaData;
    ver = MAKEWORD(1,1);//在调用WSAStartup()要告诉window,我用什么版本的socket
    WSAStartup(ver,&wsaData);//windows要求,只要用socket,第一步,必须调用这个函数
    //初始化Socket完成

    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = htonl(INADDR_ANY);//作为接收方,不需要指定具体的IP地址,接受的主机是

    int rc=0;
    if (bind(st,(struct sockaddr *)&addr,sizeof(addr)) > -1 )//将端口号和程序绑定
    {
        char buf[1024] = {0};
        struct sockaddr_in sendaddr;
        memset(&sendaddr, 0 , sizeof(sendaddr));
         int len = sizeof(sendaddr);
         
        while(1)
        {
            memset(buf, 0, sizeof(buf));
            //接受udp数据
            rc = recvfrom(st,buf,sizeof(buf),0, (struct sockaddr *)&sendaddr,&len);
            inet_ntoa(sendaddr.sin_addr);//这个函数是不可重入函数
            printf("sendIp:%s,message:%s\n",inet_ntoa(sendaddr.sin_addr),buf);
        }
    }
    closesocket(st);//使用完socket要将其关闭
    WSACleanup();//释放win socket内部相关资源
    
    return rc;
}


unsigned long getIp(const char* ip){
    unsigned long tempIp=*ip;
}

首先linux中不需要加载lib库,删除:

#pragma comment(lib,"ws2_32.lib")

其次linux不用初始化socket和释放socket,这部分代码是windows特有的,删除:

 //初始化socket
    DWORD ver;
    WSADATA wsaData;
    ver = MAKEWORD(1,1);//在调用WSAStartup()要告诉window,我用什么版本的socket
    WSAStartup(ver,&wsaData);//windows要求,只要用socket,第一步,必须调用这个函数
    //初始化Socket完成\
    ....
 WSACleanup();//释放win socket内部相关资源
 ....

WinSock2.h 找不到就直接删除包含的头文件:

#include 

以上修改完毕后,在make一下:

[root@bogon 2018-11-16]# make
g++ -o udp.o -c udp.cpp 
udp.cpp: In function ‘int socket_send(const char*)’:
udp.cpp:11: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:11: 错误:expected ‘;’ before ‘st’
udp.cpp:12: 错误:聚合‘sockaddr_in addr’类型不完全,无法被定义
udp.cpp:14: 错误:‘AF_INET’在此作用域中尚未声明
udp.cpp:15: 错误:‘htons’在此作用域中尚未声明
udp.cpp:16: 错误:‘inet_addr’在此作用域中尚未声明
udp.cpp:31: 错误:‘st’在此作用域中尚未声明
udp.cpp:31: 错误:‘sendto’在此作用域中尚未声明
udp.cpp:33: 错误:‘st’在此作用域中尚未声明
udp.cpp:33: 错误:‘closesocket’在此作用域中尚未声明
udp.cpp: In function ‘int socket_receive()’:
udp.cpp:44: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:44: 错误:expected ‘;’ before ‘st’
udp.cpp:45: 错误:聚合‘sockaddr_in addr’类型不完全,无法被定义
udp.cpp:47: 错误:‘AF_INET’在此作用域中尚未声明
udp.cpp:48: 错误:‘htons’在此作用域中尚未声明
udp.cpp:49: 错误:‘INADDR_ANY’在此作用域中尚未声明
udp.cpp:49: 错误:‘htonl’在此作用域中尚未声明
udp.cpp:52: 错误:‘st’在此作用域中尚未声明
udp.cpp:52: 错误:‘bind’在此作用域中尚未声明
udp.cpp:55: 错误:聚合‘sockaddr_in sendaddr’类型不完全,无法被定义
udp.cpp:63: 错误:‘recvfrom’在此作用域中尚未声明
udp.cpp:64: 错误:‘inet_ntoa’在此作用域中尚未声明
udp.cpp:69: 错误:‘st’在此作用域中尚未声明
udp.cpp:69: 错误:‘closesocket’在此作用域中尚未声明

会发现错误少了一些,目前提示很多**在此作用域中尚未声明,说明需要加入对应的头文件,那么如何知道该类型在Linux中对应的头文件呢,以socket为例,使用如下命令:

    [root@bogon 2018-11-16]# man socket
    SOCKET(2)                  Linux Programmer’s Manual                 SOCKET(2)
    
    NAME
           socket - create an endpoint for communication
    
    SYNOPSIS
           #include           /* See NOTES */
           #include 
    
           int socket(int domain, int type, int protocol);
    
    DESCRIPTION
           socket() creates an endpoint for communication and returns a descriptor.
    
           The domain argument specifies a communication domain; this  selects  the
           protocol  family  which  will be used for communication.  These families
           are  defined  in  .   The  currently  understood   formats
           include:
    
           Name                Purpose                          Man page
           AF_UNIX, AF_LOCAL   Local communication              unix(7)
           AF_INET             IPv4 Internet protocols          ip(7)

可以找到该类型需要的头文件,其他的以此类推,加完之后在make一下:

[root@bogon 2018-11-16]# make
g++ -o udp.o -c udp.cpp 
udp.cpp: In function ‘int socket_send(const char*)’:
udp.cpp:14: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:14: 错误:expected ‘;’ before ‘st’
udp.cpp:34: 错误:‘st’在此作用域中尚未声明
udp.cpp:36: 错误:‘st’在此作用域中尚未声明
udp.cpp:36: 错误:‘closesocket’在此作用域中尚未声明
udp.cpp: In function ‘int socket_receive()’:
udp.cpp:47: 错误:‘SOCKET’在此作用域中尚未声明
udp.cpp:47: 错误:expected ‘;’ before ‘st’
udp.cpp:55: 错误:‘st’在此作用域中尚未声明
udp.cpp:72: 错误:‘st’在此作用域中尚未声明
udp.cpp:72: 错误:‘closesocket’在此作用域中尚未声明
make: *** [udp.o] 错误 1

首先SOCKET在Linux中是int类型的,closetsocket函数在linux中是close函数,并且和上述一样推出close函数的头文件,加入close函数相应的头文件
再make一下:

[root@bogon 2018-11-16]# make
g++ -o udp.o -c udp.cpp 
udp.cpp: In function ‘int socket_receive()’:
udp.cpp:69: 错误:从类型‘int*’到类型‘socklen_t*’的转换无效
udp.cpp:69: 错误:  初始化‘ssize_t recvfrom(int, void*, size_t, int, sockaddr*, socklen_t*)’的实参 6
make: *** [udp.o] 错误 1

会发现在linux中recvfrom函数中的第6个参数是socklen_t*类型而不是int *类型,更改过来,在make:

[root@bogon 2018-11-16]# make
g++ -o udp.o -c udp.cpp 
g++ -oMyUdp main.o udp.o
udp.o: In function `socket_send(char const*)':
udp.cpp:(.text+0xa7): warning: the `gets' function is dangerous and should not be used.
main.o: In function `main':
main.cpp:(.text+0x1b): undefined reference to `socket_send'
main.cpp:(.text+0x22): undefined reference to `socket_receive'
collect2: ld 返回 1
make: *** [start] 错误 1

会发现函数socket_sendsocket_receive找不到,之前学习这部分的时候,我们已经讲过这个原理了,但是我认为这个比较重要,所以再讲解一遍。

记得之前说过.cpp编写的程序和.c编写的程序,编译器针对这两种文件编译形式是不一样的,如果函数前标明"extern C"则表示该函数需要按照.c的编译形式去寻找编译结果,但是现在我们为了makefile编译方便,已经将udp.c更名为了udp.cpp,那么编译器会按照.cpp的编译形式去编译socket_sendsocket_receive函数,但是在main.cpp中调用两个函数,main.cpp发现这两个函数使用了"extern C"标明,于是按照.c的编译形式形成的编译结果去寻找函数,于是造成了矛盾,出现了函数找不到的情况。

那么如何解决这个问题呢?
我们只需要将两个函数标明的"extern C"干掉,这样main.cpp自然会按照.cpp的编译形式形成的编译结果去寻找两个函数,结果就是正确的。

最后再make一下,发现已经是成功了:

[root@bogon 2018-11-16]# make
g++ -o main.o -c main.cpp 
g++ -o udp.o -c udp.cpp 
g++ -oMyUdp main.o udp.o
udp.o: In function `socket_send(char const*)':
udp.cpp:(.text+0xa7): warning: the `gets' function is dangerous and should not be used.

最后贴出修改后的udp.h与udp.cpp:

#ifndef UPD_H
#define UPD_H
int socket_send(const char *IP);
int socket_receive();
#endif // UPD_H
#include 
#include 

#include           /* See NOTES */
#include 
#include 
#include 

#define SOCKET int

int socket_send(const char *IP)
{

    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = inet_addr(IP);//"127.0.0.1" 这个IP代表自己
//    unsigned long laddr = inet_addr("192.168.6.200");
//    printf("%x\n",laddr);
//    unsigned char *p = &laddr;
//    printf("%u,%u,%u,%u\n",*(p),*(p+1),*(p+2),*(p+3));

    char buf[1024] = {0};
    size_t rc;
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        gets(buf);
        if(buf[0] == '0')
        break;
        //发送udp数据
        rc = sendto(st,buf,strlen(buf),0,(struct sockaddr *)&addr,sizeof(addr));
    }
    close(st);//使用完socket要将其关闭
    return rc;

}

int socket_receive()
{

    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = htonl(INADDR_ANY);//作为接收方,不需要指定具体的IP地址,接受的主机是

    int rc=0;
    if (bind(st,(struct sockaddr *)&addr,sizeof(addr)) > -1 )//将端口号和程序绑定
    {
        char buf[1024] = {0};
        struct sockaddr_in sendaddr;
        memset(&sendaddr, 0 , sizeof(sendaddr));
        socklen_t len = sizeof(sendaddr);

        while(1)
        {
            memset(buf, 0, sizeof(buf));
            //接受udp数据
            rc = recvfrom(st,buf,sizeof(buf),0, (struct sockaddr *)&sendaddr,&len);
            inet_ntoa(sendaddr.sin_addr);//这个函数是不可重入函数
            printf("sendIp:%s,message:%s\n",inet_ntoa(sendaddr.sin_addr),buf);
        }
    }

    close(st);//使用完socket要将其关闭
    return rc;
}


unsigned long getIp(const char* ip){
    unsigned long tempIp=*ip;
}

那么现在我们在linux运行该程序:

[root@bogon 2018-11-16]# ls -la
总用量 40
drwxr-xr-x. 2 root root 4096 11月 17 08:43 .
drwxr-xr-x. 8 root root 4096 11月 17 07:33 ..
-rw-r--r--. 1 root root  229 11月 17 08:19 main.cpp
-rw-r--r--. 1 root root 1680 11月 17 08:43 main.o
-rw-r--r--. 1 root root  157 11月 17 08:20 makefile
-rwxr-xr-x. 1 root root 7762 11月 17 08:43 MyUdp
-rw-r--r--. 1 root root 2680 11月 17 08:40 udp.cpp
-rw-r--r--. 1 root root  104 11月 17 08:43 udp.h
-rw-r--r--. 1 root root 2344 11月 17 08:43 udp.o
[root@bogon 2018-11-16]# ./MyUdp 

然后我们试试在windows上启动客户端去给Linux发消息:
Linux下C++编程基础_第2张图片
可以看到会发现不起作用,那么是为什么呢?是由于linux防火墙的原因,我们只需要将linux防火墙关闭即可,
关闭Linux系统的防火墙,否则Windows不能够连接Linux
(1) 重启后永久性生效: 开启:chkconfig iptables on 关闭:chkconfig iptables off
(2) 即时生效,重启后失效: 开启:service iptables start 关闭:service iptables stop

[root@bogon 2018-11-16]# service iptables stop
iptables:将链设置为政策 ACCEPT:filter                    [确定]
iptables:清除防火墙规则:                                 [确定]
iptables:正在卸载模块:                                   [确定]
[root@bogon 2018-11-16]# ./MyUdp 

然后在尝试发送,会发现已经成功Linux下C++编程基础_第3张图片
但是发送中文却还是有点问题,可以看到我发了两个中文的你好,但是却只显示了一个,因为我的SecurtCRT是设置的UTF-8格式的字符,而windows终端默认是gb2312,所有后面,我将SecurtCRT设置为gb2312之后,成功显示了windows发过来的你好
Linux下C++编程基础_第4张图片
上面我们已经成功将windows上的程序移植到了linux,并运行起来了,但是假如我们又要从linux中移植到windows呢?那岂不是又要更改一遍?这样岂不是很麻烦,那该如何做呢?

一般我们会通过宏定义去区分,当需要时,打开该宏,那么如何改呢?我们拿到原始windows上的程序,通过宏直接加入linux增加的代码,最终如下:

#include 
#include 
#include "udp.h"
#ifdef MYLINUX
#include           /* See NOTES */
#include 
#include 
#include 
#define SOCKET int
#else
#include 
#endif


#pragma comment(lib,"ws2_32.lib")
int socket_send(const char *IP)
{
#ifndef MYLINUX
    //初始化socket
    DWORD ver;
    WSADATA wsaData;
    ver = MAKEWORD(1,1);//在调用WSAStartup()要告诉window,我用什么版本的socket
    WSAStartup(ver,&wsaData);//windows要求,只要用socket,第一步,必须调用这个函数
    //初始化Socket完成
#endif
    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = inet_addr(IP);//"127.0.0.1" 这个IP代表自己
//    unsigned long laddr = inet_addr("192.168.6.200");
//    printf("%x\n",laddr);
//    unsigned char *p = &laddr;
//    printf("%u,%u,%u,%u\n",*(p),*(p+1),*(p+2),*(p+3));

    char buf[1024] = {0};
    size_t rc;
    while(1)
    {
        memset(buf, 0, sizeof(buf));
        gets(buf);
        if(buf[0] == '0')
        break;
        //发送udp数据
        rc = sendto(st,buf,strlen(buf),0,(struct sockaddr *)&addr,sizeof(addr));
    }
#ifdef MYLINUX
    close(st);
#else
    closesocket(st);//使用完socket要将其关闭
    WSACleanup();//释放win socket内部相关资源
#endif
    return rc;

}

int socket_receive()
{
#ifndef MYLINUX
    //初始化socket
    DWORD ver;
    WSADATA wsaData;
    ver = MAKEWORD(1,1);//在调用WSAStartup()要告诉window,我用什么版本的socket
    WSAStartup(ver,&wsaData);//windows要求,只要用socket,第一步,必须调用这个函数
    //初始化Socket完成
#endif

    //建立一个socket,第一个参数是指定socket要用哪个协议,AF_INET代表我要用TCP/IP协议
    //第二个参数SOCK_DGRAM意思是要用UDP协议
    //第三个参数一般默认填0
    SOCKET st = socket(AF_INET,SOCK_DGRAM,0);
    struct sockaddr_in addr;
    memset(&addr,0,sizeof(addr));//初始化结构addr
    addr.sin_family = AF_INET;//代表要使用TCP/IP的地址
    addr.sin_port = htons(8080);//host to net short
    addr.sin_addr.s_addr = htonl(INADDR_ANY);//作为接收方,不需要指定具体的IP地址,接受的主机是

    int rc=0;
    if (bind(st,(struct sockaddr *)&addr,sizeof(addr)) > -1 )//将端口号和程序绑定
    {
        char buf[1024] = {0};
        struct sockaddr_in sendaddr;
        memset(&sendaddr, 0 , sizeof(sendaddr));
#ifdef MYLINUX
         socklen_t len = sizeof(sendaddr);
#else
         int len = sizeof(sendaddr);
#endif

        while(1)
        {
            memset(buf, 0, sizeof(buf));
            //接受udp数据
            rc = recvfrom(st,buf,sizeof(buf),0, (struct sockaddr *)&sendaddr,&len);
            inet_ntoa(sendaddr.sin_addr);//这个函数是不可重入函数
            printf("sendIp:%s,message:%s\n",inet_ntoa(sendaddr.sin_addr),buf);
        }
    }

#ifdef MYLINUX
    close(st);
#else
    closesocket(st);//使用完socket要将其关闭
    WSACleanup();//释放win socket内部相关资源
#endif
    return rc;
}


unsigned long getIp(const char* ip){
    unsigned long tempIp=*ip;
}

可以看到我们通过MYLINXUX这样的宏定义与否来判断启动Linux还是windows模块的相关代码,

那么MYLINUX如何定义呢?我们需要在makefile中加入如下:

XX=g++
SRCS=main.cpp\
        udp.cpp

OBJS=$(SRCS:.cpp=.o)

EXEC=MyUdp

start:$(OBJS)
        $(XX) -o$(EXEC) $(OBJS)

.cpp.o:
        $(XX) -o $@ -c $< -DMYLINUX
clean:
        rm -rf $(OBJS)

通过-D接着宏定义的形式,因为宏定义是需要预编译,所以跟.cpp.o一起

-DMYLINUX

最后接着编译,还是没有问题:

[root@bogon 2018-11-16]# make clean
rm -rf main.o udp.o
[root@bogon 2018-11-16]# clear
[root@bogon 2018-11-16]# ls -la
总用量 32
drwxr-xr-x. 2 root root 4096 11月 17 09:11 .
drwxr-xr-x. 8 root root 4096 11月 17 07:33 ..
-rw-r--r--. 1 root root  229 11月 17 08:19 main.cpp
-rw-r--r--. 1 root root  166 11月 17 09:10 makefile
-rwxr-xr-x. 1 root root 7762 11月 17 09:11 MyUdp
-rw-r--r--. 1 root root 3693 11月 17 09:09 udp.cpp
-rw-r--r--. 1 root root  104 11月 17 08:43 udp.h
[root@bogon 2018-11-16]# make
g++ -o main.o -c main.cpp -DMYLINUX
g++ -o udp.o -c udp.cpp -DMYLINUX
g++ -oMyUdp main.o udp.o
udp.o: In function `socket_send(char const*)':
udp.cpp:(.text+0xa7): warning: the `gets' function is dangerous and should not be used.
[root@bogon 2018-11-16]# 

七、linux上编译so库,并兼容.c与.cpp的调用

在windows上我们通常想封装一些接口给其他人调用,但是却不想别人看到我们的实现,我们通常会编写一个dll,通过dll的形式提供给其他人调用,这样别人只用关心接口使用,不用关心实现,也不能修改具体实现。保证代码的安全可靠。

那么在Linux中,通常我们采用的是共享库的形式,也就是so库的形式提供给调用者,那么具体如何实现呢?

首先我们简单的申明一个比较大小的函数,通过传入两个参数,比较两个参数的大小,谁大就返回谁的形式实现它,代码如下:

[root@bogon 2018-11-18]# vim mylib.h
#ifndef MY_LIB_H
#define MY_LIB_H

int max(int a,int b);

#endif
[root@bogon 2018-11-18]# vim mylib.c
#include "mylib.h"

int max(int a,int b){
  return a>b?a:b;
}

编写好max接口后,那么该如何将它编译成一个so库的形式呢?通过我们的学习,我们都知道linux程序都是makefile来进行编译的,那么so库makefile该如何编写呢?

[root@bogon 2018-11-18]# vim makefile
XX=gcc
SRCS=mylib.c

OBJS=$(SRCS:.c=.o)

EXEC=libmylib.so

start:$(OBJS)
        $(XX) -o$(EXEC) $(OBJS) -shared

.c.o:
        $(XX) -o $@ -c $< -fPIC
clean:
        rm -rf $(OBJS)

在linux编译so,

  1. 首先so名称必须以lib开头,以so结尾
  2. 链接时必须加入-fPIC,表示这是编译so库,没有偏移位置
  3. 在 编译时,需要指定 -shared 选项,标明是一个共享库

最后编译一把,

[root@bogon 2018-11-18]# ls -la
总用量 20
drwxr-xr-x. 2 root root 4096 11月 19 05:09 .
drwxr-xr-x. 9 root root 4096 11月 19 04:55 ..
-rw-r--r--. 1 root root  161 11月 19 05:08 makefile
-rw-r--r--. 1 root root   62 11月 19 05:05 mylib.c
-rw-r--r--. 1 root root   65 11月 19 05:00 mylib.h
[root@bogon 2018-11-18]# make
gcc -o mylib.o -c mylib.c -fPIC
gcc -olibmylib.so mylib.o -shared
[root@bogon 2018-11-18]# ls -la
总用量 28
drwxr-xr-x. 2 root root 4096 11月 19 05:13 .
drwxr-xr-x. 9 root root 4096 11月 19 04:55 ..
-rwxr-xr-x. 1 root root 3876 11月 19 05:13 libmylib.so
-rw-r--r--. 1 root root  161 11月 19 05:08 makefile
-rw-r--r--. 1 root root   62 11月 19 05:05 mylib.c
-rw-r--r--. 1 root root   65 11月 19 05:00 mylib.h
-rw-r--r--. 1 root root  685 11月 19 05:13 mylib.o
[root@bogon 2018-11-18]# 

共享库制作完成,那么现在我们通过模拟一个main.c程序去调用我们编写的共享库是否可用吧:

[root@bogon 2018-11-18]# vim main.c
#include "mylib.h"
#include 

int main(){
   printf("max=%d",max(5,6));
   return 0;
}

然后在编写makefile,看看效果吧:

[root@bogon 2018-11-18]# mv makefile makefile.lib
[root@bogon 2018-11-18]# vim makefile
XX=gcc
SRCS=main.c

OBJS=$(SRCS:.c=.o)

EXEC=mylibTest

start:$(OBJS)
        $(XX) -o $(EXEC) $(OBJS)

.c.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)

好的,最后再make一下吧:

[root@bogon 2018-11-18]# make
g++ -o main.o -c main.c
g++ -o mylibTest main.o
main.o: In function `main':
main.c:(.text+0x19): undefined reference to `max(int, int)'
collect2: ld 返回 1
make: *** [start] 错误 1
[root@bogon 2018-11-18]# 

会发现max是没有定义的,因为我们想使用共享库,但是却没有在makefile中指定该共享库,因此里面的函数肯定是无法找到的,那么如何在makefile中指定共享库呢?

[root@bogon 2018-11-18]# vim makefile
XX=gcc
SRCS=main.c

OBJS=$(SRCS:.c=.o)

EXEC=mylibTest

start:$(OBJS)
        $(XX) -o $(EXEC) $(OBJS) -L. -lmylib

.c.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)
 [root@bogon 2018-11-18]# make
gcc -o main.o -c main.c
gcc -o mylibTest main.o -L. -lmylib 

-L 表示指定共享路径 .代表当前目录 -l指定共享库名称,因为linux默认认为共享库以lib开头,.so结尾,所以直接指定mylib名字即可。
最后make显然已经生成了mylibTest程序

那么现在如果我们需要将库运行在main.cpp里面呢?会有问题吗?我们试验下吧:

[root@bogon 2018-11-18]# cp main.c main.cpp
[root@bogon 2018-11-18]# vim makefile 
XX=g++
SRCS=main.cpp

OBJS=$(SRCS:.c=.o)

EXEC=mylibTest

start:$(OBJS)
        $(XX) -o $(EXEC) $(OBJS) -L. -lmylib 

.cpp.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)                          

因为.cpp需要通过g++编译,所以需要将gcc改为g++进行编译

[root@bogon 2018-11-18]# make clean
rm -rf main.o
[root@bogon 2018-11-18]# make
g++ -o main.o -c main.cpp
g++ -o mylibTest main.o -L. -lmylib 
main.o: In function `main':
main.cpp:(.text+0x19): undefined reference to `max(int, int)'
collect2: ld 返回 1
make: *** [start] 错误 1
[root@bogon 2018-11-18]# 

会发现max函数找不到,经过第6小节的学习我们知道,是因为.cpp.c的编译形式,导致编译结果不一样,main.cpp认为max函数是.cpp编译出来的,所以找不到,那么按照之前的写法我们作出更改mylib.h

[root@bogon 2018-11-18]# vim mylib.h
#ifndef MY_LIB_H
#define MY_LIB_H

extern "C"{
int max(int a,int b);
}
#endif
[root@bogon 2018-11-18]# make clean
rm -rf main.o
[root@bogon 2018-11-18]# make
g++ -o main.o -c main.cpp
g++ -o mylibTest main.o -L. -lmylib 
[root@bogon 2018-11-18]# 

再次编译即可成功,那么现在问题来了,我们假如又想在main.c中调用呢?我们试一试呢?

[root@bogon 2018-11-18]# vim makefile
XX=gcc
SRCS=main.c

OBJS=$(SRCS:.c=.o)

EXEC=mylibTest

start:$(OBJS)
        $(XX) -o $(EXEC) $(OBJS) -L. -lmylib 

.c.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)
[root@bogon 2018-11-18]# make
gcc -o main.o -c main.c
In file included from main.c:2:
mylib.h:4: 错误:expected identifier or ‘(’ before string constant
make: *** [main.o] 错误 1

会发现mylib.h是有错误的,那是因为extern "C"只在c++中才有,
那么岂不是又要删除刚刚的修改呢?那么是否有一个兼蓉的办法呢?答案是肯定的。

通过使用宏定义"__cplucplus" ,当使用.cpp作为调用者时,编译器会定义该宏定义,那么这样就简单了,可以根据该宏定义是否定义:

[root@bogon 2018-11-18]# vim mylib.h
#ifndef MY_LIB_H
#define MY_LIB_H
#ifdef __cplusplus
extern "C"{
#endif
int max(int a,int b);

#ifdef __cplusplus
}

#endif
#endif
-bash: majke: command not found
[root@bogon 2018-11-18]# make
gcc -o main.o -c main.c
gcc -o mylibTest main.o -L. -lmylib 

最后发现便可以成功了,那么再改成.cpp试试呢?

[root@bogon 2018-11-18]# vim makefile
XX=g++

SRCS=main.cpp

OBJS=$(SRCS:.cpp=.o)

EXEC=mylibTest

start:$(OBJS)
        $(XX) -o $(EXEC) $(OBJS) -L. -lmylib 

.cpp.o:
        $(XX) -o $@ -c $<
clean:
        rm -rf $(OBJS)

最后发现也是可以成功的

你可能感兴趣的:(linux)