=============2016/08/15================

上午完成makefile的试验,缩短了代码量,现在make强大,有缺省的变量,能自己推导关系,不需要gcc –MM -MG

1)需要的usr/lib等缺省目录的参数,仅仅 -lcxl, -lpthread

#LDFLAGS = -L${PWD} -L${PSLSE_DIR} -lm -lcxl -lpthread -lrt

LDFLAGS = -L${PSLSE_DIR} -lcxl –lpthread

-------------------------------------------------

2)LDLIB也是认可的参数,所以上面的一句话也可以变成如下

LDFLAGS = -L${PSLSE_DIR}

LDLIBS = -lm –lcxl –lpthread

CFLAGS += -std=gnu99 -Wall -Werror -g -Wno-comment
CPPFLAGS += -D_GNU_SOURCE
CPPFLAGS += -I${PSLSE_DIR} -I${CAPI_INC_DIR} -I${ARGCONFIG_INC_DIR} -I${CAPI_H_DIR} -I${ARGCONFIG_H_DIR} -I${CAPI_DIR} -I${ARGCONFIG_DIR}
LDFLAGS += -L${PSLSE_DIR}         
LDLIBS += -lcxl -lpthread
LIBCFLAGS += -g -Wall -m64 -DDEBUG -std=gnu99

依次顺序:
cc CFLAGS CPPFLAGS -c -o 目标.c 目标.o

然后再链接
cc CFLAGS CPPFLAGS LDFLAGS 目标.c 有关联.o LDLIBS -o 目标

cc -std=gnu99 -Wall -Werror -g -Wno-comment -D_GNU_SOURCE -I../libs/pslse/libcxl -I../libs/capi/inc -I../libs/argconfig/inc -I../libs/capi/inc/capi -I../libs/argconfig/inc/argconfig -I../libs/capi/src -I../libs/argconfig/src -L../libs/pslse/libcxl   textswap.c readthrd.o writethrd.o textswap_proc.o ../libs/capi/src/capi.o ../libs/capi/src/build_version.o ../libs/capi/src/wqueue_emul.o ../libs/capi/src/snooper.o ../libs/capi/src/wqueue.o ../libs/capi/src/worker.o ../libs/capi/src/fifo.o ../libs/capi/src/utils.o ../libs/argconfig/src/suffix.o ../libs/argconfig/src/report.o ../libs/argconfig/src/argconfig.o  -lcxl -lpthread -o textswap

CFLAGS += -std=gnu99 -Wall -Werror -g -Wno-comment
CPPFLAGS += -D_GNU_SOURCE
CPPFLAGS
+= -I${PSLSE_DIR} -I${CAPI_INC_DIR} -I${ARGCONFIG_INC_DIR}
-I${CAPI_H_DIR} -I${ARGCONFIG_H_DIR} -I${CAPI_DIR} -I${ARGCONFIG_DIR}
LDFLAGS += -L${PSLSE_DIR}
LDLIBS += -lcxl -lpthread
LIBCFLAGS += -g -Wall -m64 -DDEBUG -std=gnu99

Objects = readthrd.o writethrd.o textswap_proc.o

Objects += $(patsubst %.c,%.o,$(wildcard ${CAPI_DIR}/*.c ${ARGCONFIG_DIR}/*.c))

APP = textswap unittest searchtest lfsrtest iotest
all: ${APP}

#depends1.mk :
#       echo "begin to generate depends1.mk"
#       echo $(shell cd ${CAPI_DIR}); pwd \
#       @$(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  > depends1.mk
#
#-include depends1.mk

${APP}: ${Objects}

---------------------------------------------------------------

3)

3.1目标可以是多重,同一个目标可以写多次.

clean::

xxxxx

clean::

yyyyy

1. 双冒号规则中:

对于一个没有依赖而只有命令行的双冒号规则,当引用此目标时,规则的命令将会被无条件执行。

而普通单冒号规则,当规则的目标文件存在时,此规则的命令永远不会被执行(目标文件永远是最新的)。

3.2

执行多个目标的方法,写在all后面

如:

ALL = textswap unitest

all:${ALL}

${APP}: ${Objects}

4)-L{$LIBDIR}指向  动态库,静态库。

编译会先找动态库.so,如果没有,再找静态库.a
LDFLAGS += -L${PSLSE_DIR}
但是使用的是动态库,则需要设置$LD_LIBRARY_PATH+= ,指向.so的位置。
否则只在/usr/lib  等系统目录以及键入程序运行的目录查找

如果动态库和静态库都存在那么会优先链接动态库,如果找不到动态库,就直接使用静态库。
如果为了调试要强制使用静态库,可以在CFLAGS中加入-static (但是其缺点就是static是指所有库的目录下的静态库,如果想一部分是静态库,一部分是动态库,则无法工作
比如出现如此问题
bs/argconfig/src/report.o ../libs/argconfig/src/argconfig.o  -lcxl -lpthread -o textswap
/usr/bin/ld: cannot find -lpthread
/usr/bin/ld: cannot find -lc
collect2: error: ld returned 1 exit status
因为也调用了动态库
LDFLAGS += -L${PSLSE_DIR}
LDLIBS += -lcxl -lpthread

-lcxl,-lptheread 只有.so文件,没有.a文件

一个方法是直接指明.a,根据顺序赋值给LDLIBS,写在前方
CFLAGS += -std=gnu99 -Wall -Werror -g -Wno-comment
CPPFLAGS += -D_GNU_SOURCE
CPPFLAGS += -I${PSLSE_DIR} -I${CAPI_INC_DIR} -I${ARGCONFIG_INC_DIR} -I${CAPI_H_DIR} -I${ARGCONFIG_H_DIR} -I${CAPI_DIR} -I${ARGCONFIG_DIR}
LDFLAGS += -L${PSLSE_DIR}
LDLIBS += ${PSLSE_DIR}/libcxl.a -lpthread
LIBCFLAGS += -g -Wall -m64 -DDEBUG -std=gnu99

5)patsubst函数是吧某一个集合,完成替换

如下这样写是错误的

Objects += $(patsubst %.c,%.o,${CAPI_DIR} ${ARGCONFIG_DIR})

改为

Objects += $(patsubst %.c,%.o,$(wildcard ${CAPI_DIR}/*.c ${ARGCONFIG_DIR}/*.c))

另外再贴一次:

1、wildcard : 扩展通配符
2、notdir : 去除路径
3、patsubst :替换通配符

例子:
建立一个测试目录,在测试目录下建立一个名为sub的子目录
$ mkdir test
$ cd test
$ mkdir sub

在test下,建立a.c和b.c2个文件,在sub目录下,建立sa.c和sb.c2 个文件

建立一个简单的Makefile
src=$(wildcard *.c ./sub/*.c)
dir=$(notdir $(src))
obj=$(patsubst %.c,%.o,$(dir) )

all:
@echo $(src)
@echo $(dir)
@echo $(obj)
@echo "end"

执行结果分析:
第一行输出:
a.c b.c ./sub/sa.c ./sub/sb.c

wildcard把 指定目录 ./ 和 ./sub/ 下的所有后缀是c的文件全部展开。

第二行输出:
a.c b.c sa.c sb.c
notdir把展开的文件去除掉路径信息

第三行输出:
a.o b.o sa.o sb.o

在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o,
任何输出。
或者可以使用
obj=$(dir:%.c=%.o)
效果也是一样的。

这里用到makefile里的替换引用规则,即用您指定的变量替换另一个变量。
它的标准格式是
$(var:a=b) 或 ${var:a=b}
它的含义是把变量var中的每一个值结尾用b替换掉a

今天在研究makefile时在网上看到一篇文章,介绍了使用函数wildcard得到指定目录下所有的C语言源程序文件名的方法,这下好了,不用手工一个一个指定需要编译的.c文件了,方法如下:

SRC = $(wildcard *.c)

等于指定编译当前目录下所有.c文件,如果还有子目录,比如子目录为inc,则再增加一个wildcard函数,象这样:

SRC = $(wildcard *.c) $(wildcard inc/*.c)

yxr注:

其实 SRC = $(wildcard *.c inc/*.c)也可以,wildcard可以带多个参数

==================2016/08/12 ===========

----------------

执行shell中命令

PATH="/data/"

all:
echo ${PATH}
echo $$PATH

例子中的第一个${PATH}引用的是Makefile中的变量,而不是shell中的PATH环境变量,后者引用的事Shell中的PATH环境变量。

如果是shell的 $$ (进程id) 如何输出它?

已经了解到:$$$$ ($$->$)

在makefile里面也可以这样调shell
如: CURRENT_DIR=$(shell pwd)

contents := $(shell cat foo)
  files := $(shell echo *.c)

------------------

depends.mk deps:
        @if [ 'x${V}' = 'x' ];                                                  \
         then                                                                   \
            echo "  DEPENDS";                                                   \
         else                                                                   \
            echo $(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  \> depends.mk; \
        fi
        @$(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  > depends.mk

-include depends.mk

--------------

-include depends.mk有两个点:一是 - 表示如果没有depends.mk,也不报错

另一点是运行make的时候,强制执行depends.mk这个目标下的动作!!!!

--------------

-----强制目标-----

target:

因为没有命令,所以make target ,与makefile 所在目录下是否存在与target 同名的文件没有直接关系。

-----双冒号规则-----

target::

commands

无论makefile 所在目录下存不存在与target 同名文件,make target 导致commands 的执行,与使用'.PHONY' 定义的伪目标效果相同。

-----------------------------------------------------------------

http://blog.csdn.net/wzw88486969/article/details/11739737

如果一个规则没有命令或者依赖,而且它的目标不是一个存在的文件名,在执行此规则时,目标总会被认为是最新的。也就是说,这个规则一旦被执行,make 就认为它所表示的目标已经被更新过。当将这样的目标(FORCE)作为一个规则的依赖时(如上的 vmlinux: ),由于依赖总被认为是被更新过的,所以作为依赖所在的规则定义的命令总会被执行。

http://blog.chinaunix.net/uid-27057175-id-4432189.html

待看

?????

但是如下这句话不能运行

test:
        @echo "hello:OBJ_DIR=${OBJ_DIR}"

或者如下也不行

---------------------

test:PORCE
        @echo "hello:OBJ_DIR=${OBJ_DIR}"

PHONY+=FORCE
FORCE:

.PHONY: $(PHONY)
-------------------

但是案例可以成功的运行,见鬼?????!!!!!

LIBCAPI=${LIBCAPI_DIR}/libcapi.a
LIBARGCONFIG=${LIBARGCONFIG_DIR}/libargconfig.a
LIBCXL=${PSLSE_DIR}/libcxl.a

CXL_FLAGS_HACK=-I$(abspath ${PSLSE_DIR}) -DPAGED_RANDOMIZER=0 -g

${LIBCAPI}: FORCE
        $(MAKE) -C ${LIBCAPI_DIR}
        echo "shanon:FORCE successfully!"

===================2016/08/12

2.6 函数 (Functions)     $()
makefile 里的函数跟它的变量很相似——使用的时候,你用一个 $ 符号跟开括号,函 数名,空格后跟一列由逗号分隔的参数,最后 用关括号结束。例如,在 GNU Make 里 有一个叫 'wildcard' 的函 数,它有一个参数,功能是展开成一列所有符合由其参数 描述的文 件名,文件间以空格间隔。你可以像下面所示使用这个命令:
SOURCES = $(wildcard *.c)
这行会产生一个所有以 '.c' 结尾的文件的列表,然后存入变量 SOURCES 里。当然你不需要一定要把结果存入一个变量。
另一个有用的函数是 patsubst ( patten substitude, 匹配替 换的缩写)函数。它 需要3个参数——第一个是一个需要匹配的 式样,第二个表示用什么来替换它,第三 个是一个需要被处理的 由空格分隔的字列。例如,处理那个经过上面定义后的变量,
OBJS = $(patsubst %.c,%.o,$(SOURCES))
这行将处理所有在 SOURCES 字列中的字(一列文件名),如果它的 结尾是 '.c' ,就 用 '.o' 把 '.c' 取代。注意这里的 % 符号将匹 配一个或多个字符,而它每次所匹配 的字串叫做一个‘柄’(stem) 。 在第二个参数里, % 被解读成用第一参数所匹配的 那个柄。

-------------

它使用变量 CC 做为编译器(象我们在前面的例子),

并且传递变量 CFLAGS (给 C 编译器,C++ 编译器用 CXXFLAGS ),

CPPFLAGS ( C 预 处理器旗 标),

TARGET_ARCH (现在不用考虑这个),然后它加 入旗标 '-c' ,后面跟变量 $< (第一个依靠名),然后是旗 标 '-o' 跟变量 $@ (目的文件名)。

depends.mk deps:
        @if [ 'x${V}' = 'x' ];                                                  \
         then                                                                   \
            echo "  DEPENDS";                                                   \
         else                                                                   \
            echo $(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  \> depends.mk; \
        fi
        $(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  > depends.mk

-include depends.mk

========================================

文件需要先编译,再链接

编译

cc -c

链接

cc -o

目标:依赖文件

动作

=========

摘抄:

需要注意的是,如果相关行写成一行,“命令”之前用分号“;”隔开,如果分成多行书写的话,后续的行务必以tab字符为先导。

对于makefile 而言,空格字符和tab字符是不同的。

所有规则所在的行必须以tab键开头,而不是空格键。初学者一定对此保持警惕,因为这是新手最容易疏忽的地方,因为 几个空格键跟一个tab键在肉眼是看不出区别的,但make命令却能明察秋毫,非常敏感。

此外,如果在makefile文件中的行尾加上空格键的话,也会导致make命令运行失败。。

@的作用

应该是取消回显

@$(CC) $(CPPFLAGS) -MM -MG $(wildcard *.c) $(wildcard *.cpp)  > depends.mk

则这个命令不显示!但是会执行

ar产生静态库.a文件

libargconfig.a: $(patsubst %.c,%.o,$(wildcard *.c))

=======

在$(patsubst %.c,%.o,$(dir) )中,patsubst把$(dir)中的变量符合后缀是.c的全部替换成.o,
patsubst表示把dir中的.c文件转为.o文件

===========

减号, 横杠的作用

表示即使命令执行返回错误,仍然执行

命令出错

每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就 算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万 事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。

为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

 clean:
-rm -f *.o

还有一个全局的办法是,给make加上“-i”或是“--ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如 果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设 置。

===

多个目标,静态模式和自动依赖

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

<targets...>: <target-pattern>: <prereq-patterns ...>

   <commands>

...

targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来 说明一下吧。如果我们的<target-parrtern>定义成“%.o”,意思是我们的集合中都是以“.o”结尾的,而如果我们 的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次 定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾, 形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“\”进行转义,来标明真实的“%”字符。

看一个例子:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c

$(CC) -c $(CFLAGS) $< -o $@

上面的例子中,指明了我们的目标从$object中获 取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foobar”,并为其加下“.c”的后 缀,于是,我们的依赖目标就是“foo.cbar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就 是“foo.c bar.c”),“$@”表示目标集(也褪恰癴oo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c

$(CC) -c $(CFLAGS) foo.c -o foo.o

bar.o : bar.c

$(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:

files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c

$(CC) -c $(CFLAGS) $< -o $@

$(filter %.elc,$(files)): %.elc: %.el

emacs -f batch-byte-compile $<

$(filter%.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。

3.8 自动生成依赖性

在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:

main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些C文件包 含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情, 我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例 如,如果我们执行下面的命令:

cc -M main.c

其输出是:

main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。

gcc-M main.c的输出是:

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h \

/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h \

/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h \

/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h \

/usr/include/bits/sched.h /usr/include/libio.h \

/usr/include/_G_config.h /usr/include/wchar.h \

/usr/include/bits/wchar.h /usr/include/gconv.h \

/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h \

/usr/include/bits/stdio_lim.h

gcc-MM main.c的输出则是:

main.o: main.c defs.h

举例:

[shannon@CentOS7-64 src]$ gcc -MM textswap.c
textswap.o: textswap.c textswap.h readthrd.h writethrd.h version.h

makefile学习小结的更多相关文章

  1. flex学习小结

    接触到flex一个多月了,今天做一个学习小结.如果有知识错误或者意见不同的地方.欢迎交流指教. 画外音:先说一下,我是怎么接触到flex布局的.对于正在学习的童鞋们,我建议大家没事可以逛逛网站,看看人 ...

  2. Python 学习小结

    python 学习小结 python 简明教程 1.python 文件 #!/etc/bin/python #coding=utf-8 2.main()函数 if __name__ == '__mai ...

  3. [转]Windows平台下Makefile学习笔记

    Windows平台下Makefile学习笔记(一) 作者:朱金灿 来源:http://blog.csdn.net/clever101 决心学习Makefile,一方面是为了解决编译开源代码时需要跨编译 ...

  4. react学习小结(生命周期- 实例化时期 - 存在期- 销毁时期)

    react学习小结   本文是我学习react的阶段性小结,如果看官你是react资深玩家,那么还请就此打住移步他处,如果你想给一些建议和指导,那么还请轻拍~ 目前团队内对react的使用非常普遍,之 ...

  5. objective-c基础教程——学习小结

    objective-c基础教程——学习小结   提纲: 简介 与C语言相比要注意的地方 objective-c高级特性 开发工具介绍(cocoa 工具包的功能,框架,源文件组织:XCode使用介绍) ...

  6. pthread多线程编程的学习小结

    pthread多线程编程的学习小结  pthread 同步3种方法: 1 mutex 2 条件变量 3 读写锁:支持多个线程同时读,或者一个线程写     程序员必上的开发者服务平台 —— DevSt ...

  7. ExtJs学习笔记之学习小结LoginDemo

    ExtJs学习小结LoginDemo 1.示例:(登录界面) <!DOCTYPE html> <html> <head> <meta charset=&quo ...

  8. 点滴的积累---J2SE学习小结

    点滴的积累---J2SE学习小结 什么是J2SE J2SE就是Java2的标准版,主要用于桌面应用软件的编程:包括那些构成Java语言核心的类.比方:数据库连接.接口定义.输入/输出.网络编程. 学习 ...

  9. (转) Parameter estimation for text analysis 暨LDA学习小结

    Reading Note : Parameter estimation for text analysis 暨LDA学习小结 原文:http://www.xperseverance.net/blogs ...

随机推荐

  1. pymongo 3.3 使用笔记

    #首先安装pymongo sudo pip install pymongo || sudo easy_install pymongo #demo均在交互解释器下进行 from pymongo impo ...

  2. c语言求平面上2个坐标点的直线距离、求俩坐标直线距离作为半径的圆的面积、递归、菲波那次数列、explode

    #include <stdio.h> #include <math.h> #include <string.h> char explode( char * str ...

  3. Android 中pid与uid的作用与区别

    PID:为Process Identifier, PID就是各进程的身份标识. 程序一运行系统就会自动分配给进程一个独一无二的PID.进程中止后PID被系统回收,可能会被继续分配给新运行的程序,但是在 ...

  4. F - To the Max

    Given a two-dimensional array of positive and negative integers, a sub-rectangle is any contiguous s ...

  5. for循环相关

    循环语句是指令式编程的常见语句,Scala对其加以改进,成为适应函数式风格的利器. for循环中的变量,没有val或者var,是因为变量的类型,完全是集合中的元素的类型.作用域持续到括号结束. 在sc ...

  6. python windows终端窗口下输出编码错误

    windows简体中文版下终端默认字符集gbk,执行chcp 65001临时修改字符集. 修改默认字符集:注册表HKEY_CURRENT_USER\Console项中CodePage值修改为65001

  7. Linux分区介绍

    分区的大小主要取决于个人的选择,以下内容可能会有一定帮助:/boot - 200 MB 实际需求大约 100 MB,如果有多个内核/启动镜像同时存在,建议分配 200 或者 300 MB./ - 15 ...

  8. Android学习七:new Date使用

    1.例子 学习时间函数,并实现了简单的多个按钮监听同一个事件的方法 2.代码 代码很简单,也很清晰 package com.example.datetime; import java.text.Sim ...

  9. c#定义全局条件编译符号

    在"工程"上单机右键,"属性"--->"生成"--->"条件编译符号"后边的输入框中,输入自定义的条件编译变 ...

  10. C# 深拷贝通用方法

    C#深拷贝通用方法(引用类型的拷贝) /// <summary> /// 深度COPY /// </summary> /// <typeparam name=" ...