我们学习c的时候也频繁用到了一些库,但是库并不多,c++也有库,且比c要多,通常越高级的语言库越多,如java、python等封装越严重的语言库就更多,在学习高级语言时学习库占比变得很重,如python我们学习完他的语法才算刚刚入门,后面的提高就是库的应用。一个典型的c程序(后缀.c)可以完全被视为c++程序来编译。在c++的编译器中自动定义了一个long int型__cplusplus(双下划线)变量用来存放当前c++的版本,而c没有该变量,所以c程序可以使用宏来判断是否有定义__cplusplus符号来判断当前是c还是c++的编译环境。
c++的源文件名的常用扩展名有.cpp .cxx .cc .c .c++,头文件有.hpp .hxx .h。由此可见c++是一个c的超集, c++完全兼收了c的语法特性和标准库,所以c++编译器完全可以正常编译一个c程序。
虽然c++程序可以支持c形式包含头文件的方式并直接使用库中的函数,如stdio.h,他并不是源生支持,而是这是c++做了很多额外工作,来兼容的。所以c++并不建议我们以c的写法去包含原来c的头文件,而是推荐使用c++的库,如
ubuntu中gcc的工具链的头文件在/usr/include 在当前目录下还有一个c++的文件夹,存放的全是g++工具链的头文件。
iostream是c++的常用标准库之一,内部包含了很多我们常用的函数,为了防止库与我们自定义的名称冲突,所以他也使用了命名空间,封装的命名空间名称为std。std的声明在iostream头文件中,与众不同的是这个头文件的包含不需要.h,直接#include
using namespace std; //声明库中的命名空间,
cout << “hell word” << endl //调用其内部元素cout
cout标准输出,对应stdout,在std命名空间中。结合 <<(流操作)进行输出,可多节连接。cout涉及的头文件有
cout本质上是ostream(iostream派生类)的一个对象,暂时我们先将类简单理解成类型,对象理解成变量。也就是说cout其实是在iostream中事先定义的一个变量。
操作符 << 本质上是c语言中的左移,在iostream中进行了运算符重载(把符号功能进行了重新定义)。
示例:
int val=8;
cout << “hello word” << val << endl
我们可以把cout看成是一个输出设备,把<<看成箭头,大概意思就是把字符串按箭头方向送到cout设备即可实现输出。endl与cout一样都是std命名空间中的一个对象,我们可简单的把他理解成是\n\r的一个替代。实际上我们同样可以就就写<
流操作格式化一般使用“流操作算子”,常用的操作符见下表,他们都是在iomanip头文件中,要使用必须包含该头文件。
流操纵算子 |
作 用 |
dec(默认) |
以十进制形式输出整数 |
hex |
以十六进制形式输出整数 |
oct |
以八进制形式输出整数 |
fixed |
以普通小数形式输出浮点数 |
scientific |
以科学计数法形式输出浮点数 |
left |
左对齐,即在宽度不足时将填充字符添加到右边 |
right(默认) |
右对齐,即在宽度不足时将填充字符添加到左边 |
cin是c++中的标准输入,也是iostream头文件中定义的std命名空间。实际上标准输入与标准输出有很多点相类似,如上表的格式也同样适用于输入,数据流符号位>>右移符号,无论是输出还是输入,cout和cin都在左边,我们的变量或要操作的内容在右边。同样输入时也具有接续效果,使用变量去接收输入参数示例如下:
int a,b;
cin >> hex >> a >>b;
cin是输入的函数,hex是以十六进制接收,a和b是去接收用户输入的两个变量,第一个输入参数放在a中,第二个放在b中,用户输入时数字间使用空格隔开,输入空格时cin认为是一个结束符号。如果输入了3个数字,由于没有接收变量,cin会把第3个数字丢了。
在c学习过程中我们经常使用man指令进行函数查询,而c++的库ubuntu并没有自带,需要我们自己进行安装,然而我们并不推荐使用man手册查询,因为c++的库比较复杂,在man手册中查询并不是很好用,我们推荐使用网络资源进行查询,如下网站。
cppreference.com
file stream简称fstream,是c++中用来读写流式文件的一个类,功能与c中的fopen、fclos类似,实际上我们对文件的操作仍然离不开c中的操作函数,只不过c++进行了重新封装。fstream本质上是一个class,这个class中提供了一系列的文件操作函数。
mode - 指定打开模式。它是位掩码类型,定义下列常量:
app 每次写入前寻位到流结尾
binary 以二进制模式打开
in 为读打开
out 为写打开,如果文件不存在自动创建
trunc 在打开时舍弃流的内容
ate 打开后立即寻位到流结尾
fs.open(pach); //打开文件
count - 要读取的字符数
fs.read(read_buff,sizeof(read_buff));
count - 要写入的字符数返回值
fs.write(write_buff,sizeof(write_buff));
fs << "hello word" << endl;
mode—参考点,beg表示文件开始,end表示文件尾,cur表示指针的当前位置。
c++字符串关键字string,是c++库中的一个类,该类是STL中basic_string模板实例化得到的一个模板类。在string这个类中有很多的成员函数,这些函数都是用来处理字符串的,我们每定义一个字符串对象如string s1,那么就可以基于对象来调用成员函数来操作s1,如s1.swap(s2),就是将s1与s2的内容进行交换。
c语言严格的说是没有字符串的,我们之前用的准确的说应该叫字符数组、字符指针,有无字符串主要是看是否有字符串类型,如都有int类型,而c++或java是真的有字符串这个类型,而且为此还提供了很多关于字符串操作的函数。所以在我们c++开发的时候,要使用c++的字符串类型,而不是使用c中的字符数组。
string构造函数的方法有多种,如:
string s1 //看起来有点像是定义一个对象
string s1() //和上一个类似,但初值不同,初值为1;
string s1(“hello word”)//在构造时赋值,类似于定义变量时赋初值。
string s1(4,’x’) //初值为4个字符X
需要注意的是我们给初值时不能给初值为数字,如string s1(123);也不能给单个字符,如string(‘x’),编译时报错的数量会让你怀疑人生。起码会让你从疲惫中被吓清醒。
赋值很简单,和c中对字符指针赋值类似,如下:
string s1;
s1 = “hello word”
s1 = ‘h’
获取长度也是用string的成员函数length和size,值得注意的是size是内存空间大小,虽然每个字符默认占1个字节,实际上我们是可以通过成员函数去改变,所以length与size有时候输出结果相同,但有时候是不同的。语法:
string s1(hell word);
i=s3.length();
y=s3.size();
字符串的拼接很简单,可以直接使用+和+=,这种方法和两个变量求和很像,这都是c++的的封装和对运算符重载的结果,这种方式在c中简直都不可想象。没办法c++就是这么强大。
string s1(“1234”);
string s2(“789”);
string s3
s3 = s1 + s2;
s1 +=s2;
字符串的比较也非常简单,直接像变量一样使用!=、==、等符号即可实现。
string s1(“1234”);
string s2(“789”);
if(s1==s2)
c有很多优秀且成熟的项目和库,丢了可惜,重写没必要,所以在c++中有时候需要调用c的代码,另外就是在一个非常庞大的项目中,如操作系统,他的底层很注重运行效率,应用层很注重开发效率,所以需要使用不同的语言进行混编。
无论是c还是c++最终都会编译成.o文件,在.o文件中我们几乎无法通过对文件本身来判断源文件是c还是c++在.o文件中仅有一些符号和二进制可执行代码,所以链接的时候完全可以对他们进行相互组合,实际上两种语言的.o文件也并不是可以无缝链接的,c++支持函数名重载而c不支持,函数名重载其实就是函数名在.o文件中的表达不一样,c的.o中的函数名就是源文件的函数名,而c++的.o中的函数名是函数名后自动添加的传参类型的首字母,如void func(int a,int b),在c++的.o中的函数名称是funcii,所以在链接的时候链接不上。
解决这个问题的关键就是要让c++编译后的变量名与c相同,在c++中为我们提供了extern “C”的机制,来告诉编译器,函数名使用c的规则来命名。示例如下:
#ifdef __cplusplus //该变量是c++编译器自动添加的
extern “C”
{
#endif
void func(void); //函数声明
# ifdef __cplusplus
}
#endif
上例中只要在extern “C”的大括号内的函数声明,在c++编译器编译的时候,函数名都会按照c的标准来进行函数名重载,即中间函数名与源文件函数名相同。extern “C”是c++的语法,如果c的编译器去编译就会报错,为了提高我们的文件可移植性,即可以使用c编译器又可以使用c++编译器,所以使用了#ifdef,如果是c编译器编译,__cpluspls这个变量根本就没有所以extern根本就不会被编译进去,自然就不会报错。所以我们用c写标准程序时一定要添加extern “C”来保证我们程序的移植性。
这种情况通常出现在供应商给我的是动态库或静态库是(含.a和.h)基于c的,如果供应商给我们的.h文件中没有添加extern “C”来兼容c++的编译,我们使用g++再次混编时,会把.o文件内的函数名重载,导致.h中的函数声明是错误的,我们去调用也会失败,解决方案是,我们可以在自己的c++源文件中包含供应商的头文件时添加extern“C”,当然也可以添加到对方的.h文件中,示例如下:
extern “C”
{
include “xxx.h”
}
这种情况同样也有可能是别人给你的库文件已经按c++的方式编译了,我们要在c中调用,首先的问题是我们不知道.hpp文件在.o中函数重载后的函数名称,所以我们无法调用,有一个投机的方法就是把.o文件反汇编,就可以看到函数名,然后在c中调用即可。
然而这样做不正规,如果函数较多,我们需要一个一个的核对,容易出错。
正规的解决方案是把.o库进行再次封装,写一个c++源文件,在文件中为.h中的每一个函数建一个别名函数,来调用库中的函数,这样我们就得到了库中每个函数的别名,再在我们新建的头文件中去添加extern “C”来使其按c标准进行函数重载,编译后得到另外一个库,我们的c程序就可去调用我们新建的库来实现间接调用源库函数。
gcc man.c -lpack -ltest -L./cplus //库中库的引用的顺序错误会出现链接失败,要先上
//级库后 下级库,即被库调用的库应该排在后面。
linux下反编译在学习c的时候已讲过,然而我们视乎已经忘了,所以在此重提,步骤如下:
gcc 1xxx -c -o 2xxx.o //将1xxx只编译不链接,命名为2xxx.o
objdump -d 2xxx.o > 3xxx.i //将2xxx.o文件反编译成.i文件。
.o文件内部全部是二进制,本身也带有符号信息,只是我们看不懂二进制,所以要使用反编译器将其转换成我们能看懂的格式。
gcc 1xxx -c -o 2xxx.o //将1xxx只编译不链接,后文件名命名为2xxx.o
ar -r lib1xx.a 2xxx.o //ar是工具链,-r静态库,lib1xxx.a库名,lib开头。
g++ main.cpp -l1xxx -L. //-l指定库名,-L指定当前路径