面向对象编程基础¶
本课程入选教育部产学合作协同育人项目 课程主页:http://cpp.njuer.org 课程老师:陈明 http://cv.mchen.org
ppt和代码下载地址
git clone https://gitee.com/cpp-njuer-org/book
第1章¶
开始¶
-
“学习一门新的程序设计语言的最好方法就是练习编写程序.”
编写一个简单的C++程序¶
- 每个C++程序都必须包含一个或多个函数,其中一个名为main()的函数,它是操作系统执行程序的调用入口
int main() { return 0; }
一个函数定义包含四部分:¶
- 返回类型(return type):
- main()的返回类型必须是int
- 函数名(function name):
- 用来进行函数调用,这里的函数名是main
- 形参列表(parameter list):
- 用()包围,指出调用函数时可以使用什么样的实参,允许为空
- 函数体(function body):
- 用{}包围的语句块,定义了函数所执行的动作.这里语句块里只有一条语句return 0.
注意不要漏掉return语句后的分号; return 0 ;表示成功
编译、运行程序¶
- 如何编译程序依赖于使用的操作系统和编译器.
- 集成开发环境(Integrated Development Environment, IDE)将编译器与其它程序创建和分析工具包装在一起. 如VS2022社区版,qt等等.
- 大部分编译器都会提供命令行界面,如g++.
编译、运行程序¶
- 程序源文件(source file)命名约定
- 不同编译器使用不同的后缀名
- .h .cpp .cc .cxx .cp .c
- 在linux下命令行运行编译器g++
- 编译:
g++ test.cpp
- 编译:
g++ --std=c++11 test.cpp -o a.out
- 用 -Wall选项可对有问题的程序结构发出警告
- 运行:
./a.out
- 查看运行状态:
echo $?
熟悉g++
编译器¶
输入 g++ -v
,查看编译器版本:
Using built-in specs.
COLLECT_GCC=g++
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/9/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 9.3.0-17ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-9/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,gm2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-9 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-9-HskZEa/gcc-9-9.3.0/debian/tmp-nvptx/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 9.3.0 (Ubuntu 9.3.0-17ubuntu1~20.04)
熟悉g++
编译器¶
输入 g++-10 -v
,查看编译器版本:
Using built-in specs.
COLLECT_GCC=g++-10
COLLECT_LTO_WRAPPER=/usr/lib/gcc/x86_64-linux-gnu/10/lto-wrapper
OFFLOAD_TARGET_NAMES=nvptx-none:amdgcn-amdhsa:hsa
OFFLOAD_TARGET_DEFAULT=1
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu 10.3.0-1ubuntu1~20.04' --with-bugurl=file:///usr/share/doc/gcc-10/README.Bugs --enable-languages=c,ada,c++,go,brig,d,fortran,objc,obj-c++,m2 --prefix=/usr --with-gcc-major-version-only --program-suffix=-10 --program-prefix=x86_64-linux-gnu- --enable-shared --enable-linker-build-id --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --libdir=/usr/lib --enable-nls --enable-bootstrap --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --with-default-libstdcxx-abi=new --enable-gnu-unique-object --disable-vtable-verify --enable-plugin --enable-default-pie --with-system-zlib --enable-libphobos-checking=release --with-target-system-zlib=auto --enable-objc-gc=auto --enable-multiarch --disable-werror --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64,mx32 --enable-multilib --with-tune=generic --enable-offload-targets=nvptx-none=/build/gcc-10-S4I5Pr/gcc-10-10.3.0/debian/tmp-nvptx/usr,amdgcn-amdhsa=/build/gcc-10-S4I5Pr/gcc-10-10.3.0/debian/tmp-gcn/usr,hsa --without-cuda-driver --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu --with-build-config=bootstrap-lto-lean --enable-link-mutex
Thread model: posix
Supported LTO compression algorithms: zlib zstd
gcc version 10.3.0 (Ubuntu 10.3.0-1ubuntu1~20.04)
熟悉g++
编译器¶
输入 g++ --help
,查看编译器选项:
Usage: g++ [options] file...
Options:
-pass-exit-codes Exit with highest error code from a phase.
--help Display this information.
--target-help Display target specific command line options.
--help={common|optimizers|params|target|warnings|[^]{joined|separate|undocumented}}[,...].
Display specific types of command line options.
(Use '-v --help' to display command line options of sub-processes).
--version Display compiler version information.
-dumpspecs Display all of the built in spec strings.
-dumpversion Display the version of the compiler.
-dumpmachine Display the compiler's target processor.
-print-search-dirs Display the directories in the compiler's search path.
-print-libgcc-file-name Display the name of the compiler's companion library.
-print-file-name=<lib> Display the full path to library <lib>.
-print-prog-name=<prog> Display the full path to compiler component <prog>.
-print-multiarch Display the target's normalized GNU triplet, used as
a component in the library path.
-print-multi-directory Display the root directory for versions of libgcc.
-print-multi-lib Display the mapping between command line options and
multiple library search directories.
-print-multi-os-directory Display the relative path to OS libraries.
-print-sysroot Display the target libraries directory.
-print-sysroot-headers-suffix Display the sysroot suffix used to find headers.
-Wa,<options> Pass comma-separated <options> on to the assembler.
-Wp,<options> Pass comma-separated <options> on to the preprocessor.
-Wl,<options> Pass comma-separated <options> on to the linker.
-Xassembler <arg> Pass <arg> on to the assembler.
-Xpreprocessor <arg> Pass <arg> on to the preprocessor.
-Xlinker <arg> Pass <arg> on to the linker.
-save-temps Do not delete intermediate files.
-save-temps=<arg> Do not delete intermediate files.
-no-canonical-prefixes Do not canonicalize paths when building relative
prefixes to other gcc components.
-pipe Use pipes rather than intermediate files.
-time Time the execution of each subprocess.
-specs=<file> Override built-in specs with the contents of <file>.
-std=<standard> Assume that the input sources are for <standard>.
--sysroot=<directory> Use <directory> as the root directory for headers
and libraries.
-B <directory> Add <directory> to the compiler's search paths.
-v Display the programs invoked by the compiler.
-### Like -v but options quoted and commands not executed.
-E Preprocess only; do not compile, assemble or link.
-S Compile only; do not assemble or link.
-c Compile and assemble, but do not link.
-o <file> Place the output into <file>.
-pie Create a dynamically linked position independent
executable.
-shared Create a shared library.
-x <language> Specify the language of the following input files.
Permissible languages include: c c++ assembler none
'none' means revert to the default behavior of
guessing the language based on the file's extension.
Options starting with -g, -f, -m, -O, -W, or --param are automatically
passed on to the various sub-processes invoked by g++. In order to pass
other options on to these processes the -W<letter> options must be used.
For bug reporting instructions, please see:
<file:///usr/share/doc/gcc-9/README.Bugs>.
输入 g++ -v --help
可以看到更完整的指令.
初识输入输出¶
- iostream库包含istream输入流和ostream输出流.一个流就是一个字符序列,随着时间的推移字符是顺序生成或者消耗的.
- 标准输入输出对象
- 标准输入,名为cin 的istream类型对象
- 标准输出,名为cout的ostream类型对象
- 另两个ostream对象
- 标准错误,cerr,输出警告与错误
- clog,输出程序运行的一般信息
一个使用IO库的例子¶
#include <iostream>
int main() {
std::cout << "Enter two numbers:" << std::endl;
int v1 = 0, v2 = 0;
std::cin >> v1 >> v2;
std::cout << "The sum of " << v1 << " and " << v2
<< " is " << v1 + v2 << std::endl;
return 0;
}
#include <iostream>
告诉编译器使用iostream库,一般包含来自标准库的头文件使用<>,不属于标准库的头文件用双引号" "
.
endl
:操纵符,结束当前行,将与设备关联的缓冲区(buffer)中的内容刷到设备中;std::
:cin、cout和endl所在std
命名空间。作用运算符::
.- 在头文件后加一行
using namespace std;
引入std名空间,其后的cin cout endl 就可不必加std::
-
谷歌编程规范建议不要使用
using namespace std;
,防止命名冲突。 -
<<
:输出运算符,将右侧的运算对象的值写到左侧运算对象中 - 比如
cout << v1;
,左侧运算对象cout是一个ostream对象,并返回其左侧运算对象,因此<<
可以连写,如cout<<v1<<v2;
std::cout << "Enter two numbers:" << std::endl;
等价于
std::cout << "Enter two numbers:";
std::cout << std::endl;
>>
输入运算符,从左侧istream读入数据,存入右侧对象。与输出运算符类似,其返回左侧运算对象,因此>>
可以连写,如
std::cin >> v1 >> v2;
等价于
std::cin >> v1;
std::cin >> v2;
编译运行结果¶
$ g++ test.cpp
$ ./a.out
Enter two numbers:
3 7
The sum of 3 and 7 is 10
练习题¶
编写程序,在标准输出上打印Hello,world.
注释简介¶
- C++中注释的种类
- 单行注释以”//"开始,以换行符结束
- 界定符注释,即多行注释,继承自C语言,以
/*
开始,以*/
结束。此注释方式不可嵌套.
/*
*注释对/* */不能嵌套.
*不能嵌套几个字会被认为是源码,
*像剩余程序一样处理。
*/
int main() {
// 这是单行注释
/*
* 这是多行注释
* 多行注释不可嵌套
* /
// /*
// *单行注释中任何内容都会被忽略
// * 包括嵌套的注释对也会被忽略
// */
return 0;
}
练习题¶
下面输出语句合法吗?
std::cout<<"/*";
std::cout<<"*/";
std::cout<</* "*/" */; //(error)
std::cout<</* "*/" /* "/*" */;
控制流¶
while语句¶
循环执行一段代码,直到给定的条件(condition)为假.
//求1到10这10个数的和
#include <iostream>
int main() {
int sum = 0, val = 1;
//只要val<=10,while循环就会持续执行
while(val <= 10) {
sum += val; // 复合运算符(+=),等价于“sum = sum + val”
++val; // 前缀自增运算符(++),等价于“val = val + 1”
}
std::cout << "Sum of 1 to 10 inclusive is "
<< sum << std::endl;
return 0;
}
编译运行结果:
$ g++ while.cpp
$ ./a.out
Sum of 1 to 10 inclusive is 55
- while 语句:
- 重复执行语句块statement,直至condition为假
while (condition) statement;
练习题¶
编写程序,使用while循环将50到100整数相加
for语句¶
for(init-statement; condition; expression) statement; - 由三部分组成: - 一个初始化语句(init-statement) - 一个循环条件(condition) - 一个表达式(expression)
//用for语句重写从1加到10
#include <iostream>
int main(){
int sum=0;
//从1加到10
for(int val=1;val<=10;++val){
sum+=val;//等价于sum=sum+val;
}
std::cout <<"Sum of 1 to 10 inclusive is"
<<sum<<std::endl;
return 0;
}
// Sum of 1 to 10 inclusive is 55
练习题¶
编写程序,使用for循环将50到100整数相加
读取数量不定的输入数据¶
// 读取数量不定的输入数据
#include <iostream>
int main() {
int sum = 0, val = 0;
// 读取数据直到文件尾,计算所有读入值的和
while(std::cin>>val) {
sum += val; // 复合运算符(+=),等价于“sum = sum + val”
}
std::cout << "Sum is "
<< sum << std::endl;
return 0;
}
ctrl+d
,
- Windows下:ctrl+z
ENTER
练习题¶
编写程序,使用cin读一组数,输出其和.
if语句:根据condition条件进行执行。如果条件为真,执行if语句体;否则,执行else语句体(如果存在的话)¶
if(condition)
statement;
或者
if(condition)
statement;
else
statement;
或者
if(condition)
statement;
else if (condition)
statement;
...
else
statement;
统计输入中每个值连续出现的次数¶
#include <iostream>
int main() {
// currval:保存正在统计的数;val:保存将读入的新值
int currval = 0, val = 0;
if (std::cin >> currval) {
// 保存当前处理值出现的次数
int cnt = 1;
while (std::cin >> val) {
if (val == currval) // 新读取的值与当前值相同,计数+1
++cnt;
else
{// 否则打印当前值次数,记住新值并重新开始计数
std::cout << currval << " occurs "
<< cnt << " times " << std::endl;
currval = val;
cnt = 1;
}
}//while
//最后一个值的个数
std::cout << currval << " occurs "
<< cnt << " times " << std::endl;
}//if
return 0;
}
类简介¶
- C++ 中我们通过定义一个类来定义自己的数据结构。
- 一个类
- 定义了一个类型
- 以及与其关联的一组操作。
- 为了使用类
- 类名是什么
- 在哪里定义
- 支持什么操作
Sales_item类 - 由Sales_item.h定义 - 封装了Sales_item的可用操作 - isbn函数 - 重载操作符 >> << 来读写 Sales_item对象 - 重载操作符 = + +=
//Sales_item.h
// 在类和重载操作符的章节会解释,目前不需要理解该文件。
/* This file defines the Sales_item class used in chapter 1.
* The code used in this file will be explained in
* Chapter 7 (Classes) and Chapter 14 (Overloaded Operators)
* Readers shouldn't try to understand the code in this file
* until they have read those chapters.
*/
#ifndef SALESITEM_H
// we're here only if SALESITEM_H has not yet been defined
#define SALESITEM_H
// Definition of Sales_item class and related functions goes here
#include <iostream>
#include <string>
class Sales_item {
// these declarations are explained section 7.2.1, p. 270
// and in chapter 14, pages 557, 558, 561
friend std::istream& operator>>(std::istream&, Sales_item&);
friend std::ostream& operator<<(std::ostream&, const Sales_item&);
friend bool operator<(const Sales_item&, const Sales_item&);
friend bool
operator==(const Sales_item&, const Sales_item&);
public:
// constructors are explained in section 7.1.4, pages 262 - 265
// default constructor needed to initialize members of built-in type
#if defined(IN_CLASS_INITS) && defined(DEFAULT_FCNS)
Sales_item() = default;
#else
Sales_item(): units_sold(0), revenue(0.0) { }
#endif
Sales_item(const std::string &book):
bookNo(book), units_sold(0), revenue(0.0) { }
Sales_item(std::istream &is) { is >> *this; }
public:
// operations on Sales_item objects
// member binary operator: left-hand operand bound to implicit this pointer
Sales_item& operator+=(const Sales_item&);
// operations on Sales_item objects
std::string isbn() const { return bookNo; }
double avg_price() const;
// private members as before
private:
std::string bookNo; // implicitly initialized to the empty string
#ifdef IN_CLASS_INITS
unsigned units_sold = 0; // explicitly initialized
double revenue = 0.0;
#else
unsigned units_sold;
double revenue;
#endif
};
// used in chapter 10
inline
bool compareIsbn(const Sales_item &lhs, const Sales_item &rhs)
{ return lhs.isbn() == rhs.isbn(); }
// nonmember binary operator: must declare a parameter for each operand
Sales_item operator+(const Sales_item&, const Sales_item&);
inline bool
operator==(const Sales_item &lhs, const Sales_item &rhs)
{
// must be made a friend of Sales_item
return lhs.units_sold == rhs.units_sold &&
lhs.revenue == rhs.revenue &&
lhs.isbn() == rhs.isbn();
}
inline bool
operator!=(const Sales_item &lhs, const Sales_item &rhs)
{
return !(lhs == rhs); // != defined in terms of operator==
}
// assumes that both objects refer to the same ISBN
Sales_item& Sales_item::operator+=(const Sales_item& rhs)
{
units_sold += rhs.units_sold;
revenue += rhs.revenue;
return *this;
}
// assumes that both objects refer to the same ISBN
Sales_item
operator+(const Sales_item& lhs, const Sales_item& rhs)
{
Sales_item ret(lhs); // copy (|lhs|) into a local object that we'll return
ret += rhs; // add in the contents of (|rhs|)
return ret; // return (|ret|) by value
}
std::istream&
operator>>(std::istream& in, Sales_item& s)
{
double price;
in >> s.bookNo >> s.units_sold >> price;
// check that the inputs succeeded
if (in)
s.revenue = s.units_sold * price;
else
s = Sales_item(); // input failed: reset object to default state
return in;
}
std::ostream&
operator<<(std::ostream& out, const Sales_item& s)
{
out << s.isbn() << " " << s.units_sold << " "
<< s.revenue << " " << s.avg_price();
return out;
}
double Sales_item::avg_price() const
{
if (units_sold)
return revenue/units_sold;
else
return 0;
}
#endif
避免多次包含同一头文件:
#ifndef SALESITEM_H
#define SALESITEM_H
// Definition of Sales_item class and related functions goes here
#endif
定义类Sales_item的对象item
Sales_item item;
读写Sales_item
//item_io.cpp
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item book;
// read ISBN, number of copies sold, and sales price
std::cin >> book;
// write ISBN, number of copies sold, total revenue, and average price
std::cout << book << std::endl;
return 0;
}
$ g++ ./item_io.cpp && ./a.out
asdf 4 24.99
asdf 4 99.96 24.99
Sales_item对象加法
//addItems.cpp
#include <iostream>
#include "Sales_item.h"
int main() {
Sales_item item1, item2;
std::cin >> item1 >> item2; //读取一对交易记录
std::cout << item1 + item2 << std::endl; //打印它们的和
return 0;
}
$ g++ ./addItems.cpp && ./a.out
asdf 4 44
asdf 5 50
asdf 9 426 47.3333
//使用文件重定向
$ echo "asdf 4 44 asdf 5 50" >> infile
$ ./a.out <infile >outfile && cat outfile
asdf 9 426 47.3333
初识成员函数¶
//addItems2.cpp
#include <iostream>
#include "Sales_item.h"
int main()
{
Sales_item item1, item2;
std::cin >> item1 >> item2;
// 检查isbn号是否相同
if (item1.isbn() == item2.isbn()) {
std::cout << item1 + item2 << std::endl;
return 0; // 表示成功
} else {
std::cerr << "Data must refer to same ISBN"
<< std::endl;
return -1; // 表示失败
}
}
$ g++ ./addItems2.cpp && ./a.out
asdf 12 22
asdf 6 20
asdf 18 384 21.3333
$ g++ ./addItems2.cpp && ./a.out
asdf 12 22
sdf 6 20
Data must refer to same ISBN
成员函数(类方法):
- 定义为类的一部分的函数
- 使用.
调用.
//名为item1的对象的isbn方法
item1.isbn()
书店程序¶
读取销售记录,生成销售报告: - 假设每个isbn号的记录聚在一起保存
//avg.cpp
#include <iostream>
#include "Sales_item.h"
int main() {
Sales_item total; // variable to hold data for the next transaction
// read the first transaction and ensure that there are data to process
if (std::cin >> total) {
Sales_item trans; // variable to hold the running sum
// read and process the remaining transactions
while (std::cin >> trans) {// if we're still processing the same book
if (total.isbn() == trans.isbn())
total += trans; // update the running total
else {// print results for the previous book
std::cout << total << std::endl;
total = trans; // total now refers to the next book
}}
std::cout << total << std::endl; // print the last transaction
} else {// no input! warn the user
std::cerr << "No data?!" << std::endl;
return -1; // indicate failure
}
return 0;}
$ g++ ./avg.cpp && ./a.out
asdf 12 22
asdf 10 23
sdf 12 11
asdf 22 494 22.4545
sdf 12 132 11
$ echo "asdf 12 22 asdf 10 23 adf 12 11 " > infile &&
./a.out <infile >outfile &&cat outfile
asdf 22 494 22.4545
adf 12 132 11
练习¶
编译并运行书店程序.
实践课¶
- 本场景将使用一台配置了Aliyun Linux 2的ECS实例(云服务器)
- 使用Vim编辑C++代码
- 使用g++编译运行这段代码
- 编辑一个 README.md 文档,键入本次实验心得。
- 云服务器(Elastic Compute Service,简称ECS) - Aliyun Linux 2是阿里云推出的 Linux 发行版 - Vim是从vi发展出来的一个文本编辑器。 - g++ 是c++编译器
C++代码¶
#include <iostream>
int main() {
std::cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
return 0;
}
#include <iostream>
using namespace std;
int main() {
cout << "!!!Hello World!!!" << endl; // prints !!!Hello World!!!
return 0;
}
附加题:¶
- 借助Sales_item.h头文件(slide内有提供),使用本机环境或阿里云提供的编程环境(IDE界面,图形用户界面,shell界面均可),编译并运行最后一个书店程序avg.cpp
实践课¶
- 打开课程主页 cpp.njuer.org
- 点击本课程阿里云平台实验网址
- 选择实验一开始体验 (提示注册阿里云账号并登录)
- 单击屏幕右侧创建资源(本次实验2小时)
-
资源创建完毕后, 使用命令安装git g++ vim工具
yum install -y git gcc-c++ vim
-
在终端内使用VIM编辑一个C++程序,可参照vim说明帮助
使用g++编译,并执行, 编译命令 g++ 文件名 执行 ./a.out
提交¶
- 截图或复制文字,提交到群作业。
- 填写网页实验报告栏,并将报告分享给阿里云uid 1675128494019369 。并将报告链接填入 https://www.aliwork.com/o/cpphomework
- 填写问卷调查 https://rnk6jc.aliwork.com/o/cppinfo
vim 共分为三种模式¶
- 命令模式
- 刚启动 vim,便进入了命令模式。其它模式下按ESC,可切换回命令模式
- i 切换到输入模式,以输入字符。
- x 删除当前光标所在处的字符。
- : 切换到底线命令模式,可输入命令。
- 输入模式
- 命令模式下按下i就进入了输入模式。
- ESC,退出输入模式,切换到命令模式
- 底线命令模式
- 命令模式下按下:(英文冒号)就进入了底线命令模式。
- wq 保存退出
vim 常用按键说明¶
除了 i, Esc, :wq 之外,其实 vim 还有非常多的按键可以使用。命令模式下:
- 光标移动
- j下 k上 h左 l右
- w前进一个词 b后退一个词
- Ctrl+d 向下半屏 ctrl+u 向上半屏
- G 移动到最后一行 gg 第一行 ngg 第n行
- 复制粘贴
- dd 删一行 ndd 删n行
- yy 复制一行 nyy复制n行
- p将复制的数据粘贴在下一行 P粘贴到上一行
- u恢复到前一个动作 ctrl+r重做上一个动作
- 搜索替换
- /word 向下找word ?word 向上找
- n重复搜索 N反向搜索
- :1,$s/word1/word2/g从第一行到最后一行寻找 word1 字符串,并将该字符串
取代为 word2
vim 常用按键说明¶
底线命令模式下:
- :set nu 显示行号
- :set nonu 取消行号
- :set paste 粘贴代码不乱序
【注:把caps lock按键映射为ctrl,能提高编辑效率。】
Markdown 文档语法¶
# 一级标题
## 二级标题
*斜体* **粗体**
- 列表项
- 子列表项
> 引用
[超链接](http://asdf.com)
![图片名](http://asdf.com/a.jpg)
|表格标题1|表格标题2|
|-|-|
|内容1|内容2|