嵌入式linux C++语言(二)——C++对C语言基础语法的扩展
C++是基于C语言扩展发展而来的面向对象的程序设计语言,本文将主要讨论C++语言基于C语言扩展的方面。
一、类型增强
1、类型检查更严格
在C语言中:
const int a = 100;
int *p = &a;
在C++语言中:
const int a = 100;//必须在定义的时候初始化
const int *p = &a;
在C++语言中不能隐式转换数据类型。
error: invalid conversion from ‘const int*‘ to ‘int*‘ [-fpermissive]
2、bool类型
C语言中表示真用非0,假用0
C++定义了自己的bool类型,真为true,假为false
sizeof(bool) = 1
3、枚举
C语言中枚举本质就是整型,枚举变量可以用任意整型赋值。而c++中枚举变量,只能用被枚举出来的元素初始化。
在C语言中,枚举的使用
#include <stdio.h>
enum weekday
{
monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main(int argc, char **argv)
{
enum weekday day = monday;
enum weekday a = sunday;
enum weekday b = 100;
//weekday c = sunday;错误用法,需要使用enum声明
printf("%d %d %d\n", day, a, b);
return 0;
}
在C++语言中枚举的使用
enum weekday
{
monday,tuesday,wednesday,thursday,friday,saturday,sunday
};
int main()
{
weekday day = sunday;
enum weekday a = monday;
cout<<day<<" "<<a<<endl;
return 0;
}
4、表达式做左值
C语言中表达式不可以做左值,C++中某些表达式可以做左值。
(a = b) = 6;
二、输入与输出
1、cin&cout
cin和cout是C++的标准输入流和输出流,在头文件 iostream 中定义。
流名 含义 隐含设备 流名 含义 隐含设备
cin 标准输入 键盘 cerr 标准错误输出 屏幕
cout 标准输出 屏幕 clog cerr 的缓冲输出 屏幕
int main()
{
char name[30];
int age;
cout<<"pls input name and age:"<<endl;
cin>>name;
cin>>age;
cout<<"your name is: "<<name<<endl;
cout<<"your age is: "<<age<<endl;
return 0;
}
2、格式化
A、按进制输出数据类型
cout<<dec<<i<<endl;
cout<<hex<<i<<endl;
cout<<oct<<i<<endl;
B、设置域宽,设置左右对齐及填充字符
int main()
{
cout<<setw(10)<<1234<<endl;
cout<<setw(10)<<setfill(‘0‘)<<1234<<endl;
cout<<setw(10)<<setfill(‘0‘)<<setiosflags(ios::left)<<1234<<endl;
cout<<setw(10)<<setfill(‘-‘)<<setiosflags(ios::right)<<1234<<endl;
return 0;
}
C、实型数据的设置
cout<<setw(5)<<‘a‘<<endl<<setw(5)<<100<<endl
<<setprecision(2)<<setiosflags(ios::fixed)<<120.00<<endl;
三、函数重载
C语言中不允许重名函数的存在,C++语言为了简化编程允许重名函数的存在,重名函数称为函数重载。
int abs(int a)
{
return a>0? a:-a;
}
double abs(double a)
{
return a>0? a:-a;
}
1、重载规则
A、函数名相同。
B、参数个数不同,参数的类型不同,参数顺序不同,均可构成重载。
C、返回值类型不同则不可以构成重载。
2、函数重载的匹配规则
A、严格匹配,找到则调用。
B、通过隐式转换寻求一个匹配,找到则调用。
C++允许int到long和double的隐式类型转换,因此遇到这种情型,则会引起二义性,解决方法是在调用时强转类型。
3、函数重载的底层实现
C++利用 name mangling(倾轧)技术,来改名函数名,区分参数不同的同名函数。
实现原理:用 vci f l d表示void char int float long double及其引用。
void func(char a); // func_c(char a)
void func(char a, int b, double c);//func_cid(char a, int b, double c);
name mangling发生在两个阶段,.cpp编译阶段,和.h的声明阶段。只有两个阶段同时进行,才能匹配调用。
C++完全兼容C语言,因此必须完全兼容C的类库。由于.c文件的类库文件中函数名并没有发生name manling行为,而在包含.c文件所对应的.h文件时,.h 文件要发生name manling行为,因而会在编译链接时候发生错误。
C++为了避免上述错误的发生,重载了关键字extern。只需要要避免name manling的函数前,加extern "C"如有多个,则extern "C"{}。
C语言标准库中实际上对C++语言程序引用时做了特殊处理,在C++语言编译器编译时使用extern "C"将C语言的标准库函数排除了命名倾轧。
#ifdef __cplusplus
extern “C”{
#endif
标准库函数
#ifdef __cplusplus
}
#endif
四、操作符重载
C++提供了运算符重载机制。可以为自定义数据类型重载运算符。实现构造数据类型也可以像基本数据类型一样的运算特性。
struct COMP
{
float real;
float image;
};
COMP operator+(COMP one, COMP another)
{
one.real += another.real;
one.image += another.image;
return one;
}
int main()
{
COMP c1 = {1,2};
COMP c2 = {3,4};
COMP sum = operator+(c1,c2); //c1+c2;
cout<<sum.real<<" "<<sum.image<<endl;
return 0;
}
实例代码重载了一个全局的操作符+号用于实现将两个自定义结构体类型相加。本质是函数的调用。
五、默认参数
函数在调用时,形参从实参那里取得值。对于多次调用用一函数同一实参时,C++给出了更简单的处理办法。给形参以默认值,这样就不用从实参那里取值了。
1、默认参数的规则
A、默认参数的顺序,是从右向左,不能跳跃。
B、定义在前,调用在后(此时定义和声明为一体),默认认参数在定义处;声明在 前,调用在后,默认参数在声明处。
C、一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数。
2、使用示例
A、单个参数
void weatherForcast(char * w="sunny")
{
time_t t = time(0);
char tmp[64];
strftime( tmp, sizeof(tmp), "%Y/%m/%d %X %A ",localtime(&t) );
cout<<tmp<< "today is weahter "<<w<<endl;
}
int main()
{
//sunny windy cloudy foggy rainy
weatherForcast();
weatherForcast("rainny");
weatherForcast();
return 0;
}
B、多个参数
float volume(float length, float weight = 4,float high = 5)
{
return length*weight*high;
}
int main()
{
float v = volume(10);
float v1 = volume(10,20);
float v2 = volume(10,20,30);
cout<<v<<endl;
cout<<v1<<endl;
cout<<v2<<endl;
return 0;
}
六、引用
变量名,本身是一段内存的引用,即别名(alias)。引用,是为己有变量起一个别名。
int a;
int &b = a;
1、引用的规则
A、引用没有定义,是一种关系型声明。声明它和原有某一变量(实体)的关系。故 而类型与原类型保持一致,且不分配内存。与被引用的变量有相同的地址。
B、声明的时候必须初始化,一经声明,不可变更。
C、可对引用再次引用。多次引用的结果,是某一变量具有多个别名。
D、&符号前有数据类型时,是引用。其它皆为取地址。
int a,b;
int &r = a;
int &r = b; //错误,不可更改原有的引用关系
r = b;//正确,赋值
float &rr = b; //错误,引用类型不匹配
cout<<&a<<&r<<endl; //变量与引用具有相同的地址。
int &ra = r; //可对引用更次引用,表示 a 变量有两个别名,分别是 r 和 ra
2、引用的应用
void swap(int &a, int &b)
{
int tmp;
tmp = a;
a = b;
b = tmp;
}
void swap(char * &a, char * &b)
{
char *t = a;
a = b;
b = t;
}
3、引用的提高
A、可以定义指针的引用,但不能定义引用的引用。
int a;
int* p = &a;
int*& rp = p; // ok
int& r = a;
int&& rr = r; // error
B、可以定义指针的指针(二级指针),但不能定义引用的指针。
int a;
int* p = &a;
int** pp = &p; // ok
int& r = a;
int&* pr = &r; // error
C、可以定义指针数组,但不能定义引用数组,可以定义数组引用。
int a, b, c;
int* parr[] = {&a, &b, &c}; // ok
int& rarr[] = {a, b, c}; // error
int arr[] = {1, 2, 3};
int (&rarr)[3] = arr; // ok 的
E、常引用
const对象的引用必须是const的,将普通引用绑定到const对象是不合法的。const引用可使用相关类型的对象(常量,非同类型的变量或表达式)初始化,是const引用与普通引用最大的区别。 const int &a=2;是合法的。 double x=3.14; constint &b=a;也是合法的。
非const引用只能绑定到与该引用同类型的对象。
4、引用的本质
引用的本质是一个常量指针。
七、new/delete
C语言中提供了malloc和free两个系统函数,完成对堆内存的申请和释放。而C++则提供了两关键字new和delete 。
1、new
A、开辟单变量地址空间
int *p = new int; //开辟大小为 sizeof(int)空间
int *a = new int(5); //开辟大小为 sizeof(int)空间,并初始化为 5
B、开辟数组空间
一维: int *a = new int[100];开辟一个大小为 100 的整型数组空间
二维: int (*a)[6] = new int[5][6]
三维: int (*a)[5][6] = new int[3][5][6]
2、delete
A、释放单变量空间
int *a = new int;
delete a; //释放单个 int 的空间
B、释放数组空间
int *a = new int[5];
delete []a; //释放 int 数组空间
3、new/delete应用
int *p = new int(5);
cout<<*p<<endl;
delete p;
char *pp = new char[10];
strcpy(pp,"china");
cout<<pp<<endl;
delete []pp;
string *ps = new string("china");
cout<<*ps<<endl; //cout<<ps<<endl;
delete ps;
char **pa= new char*[5];
memset(pa,0,sizeof(char*[5]));
pa[0] = "china";
pa[1] = "america";
char **pt = pa;
while(*pt)
{
cout<<*pt++<<endl;
}
delete []pt;
int (*qq)[3][4] = new int[2][3][4];
delete []qq;
4、返回值
//C++ 内存申请失败会抛出异常
try{
int *p = new int[10];
}
catch(const std::bad_alloc e)
{
return -1;
}
//C++ 内存申请失败不抛出异常版本
int *q = new (std::nothrow)int[10];
if(q == NULL)
return -1;
5、注意事项
A、new/delete 是关键字,效率高于 malloc 和 free.
B、配对使用,避免内存泄漏和多重释放。
C、避免交叉使用。比如 malloc 申请的空间去 delete,new 出的空间被 free;
D、重点用在类对像的申请与释放。申请的时候会调用构造器完成初始化,
释放的时候,会调用析构器完成内存的清理。
八、内联函数
C语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。C++提供了inline关键字,实现了真正的内嵌,优点是高度抽象,避免重复开发,具备类型检查,缺点是压栈与出栈,带来开销。
inline int sqr(int x)
{
return x*x;
}
优点:避免调用时的额外开销(入栈与出栈操作)
缺点:内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
适用场景:函数体很“小”,且被“频繁”调用。
inline实际上是是对编译器的建议,如果编译器认为inline声明的函数可以内联,则编译器会将函数内联,如果编译器认为inline声明的函数的函数体太长,则不会内联,按普通函数处理。
九、类型强转
C语言中的类型转换是强制转换,
1、静态类型转换
静态类型转换是在编译期内即可决定其类型的转换。
语法格式:
static_cast<目标类型> (标识符)
int a = 10;
int b = 3;
cout<<static_cast<float>(a)/b<<endl;
2、动态类型转换
语法格式:
dynamic_cast<目标类型> (标识符)
用于多态中的父子类之间的强制转化
2、常量类型转换
语法格式:
const_cast<目标类型> (标识符) //目标类类型只能是指针或引用。
const_cast将转换掉表达式的const属性
4、重解释类型转换
语法格式:
reinterpret_cast<目标类型> (标识符)
为数据的二进制形式重新解释,但是不改变其值。
十、命名空间
命名空间为了大型项目开发,而引入的一种避免命名冲突的一种机制。比如说,在一个大型项目中,要用到多家软件开发商提供的类库。在事先没有约定的情况下,两套类库可能在存在同名的函数或是全局变量而产生冲突。项目越大,用到的类库越多,开发人员越多,这种冲突就会越明显。
1、默认NameSpace(global &&function)
global scope是一个程序中最大的scope,是引起命名冲突的根源。C语言没有从语言层面提供这种机制来解决。global scope是无名的命名空间。
2、语法规则
NameSpace是对全局区域的再次划分。
A、声明及namespace 中可以包含的内容
namespace NAMESPACE
{
全局变量int a;
数据类型struct Stu{};
函数 void func();
}
B、使用方法
直接指定 命名空间: NameSpace::a = 5;
使用 using+命名空间+空间元素:using NameSpace::a; a = 2000;
使用 using +namespace+命名空间;
C、无可避免的冲突
#include <iostream>
using namespace std;
namespace MySpace
{
int x = 1;
int y = 2;
}
namespace Other {
int x = 3;
int y = 4;
}
int main()
{
{
using namespace MySpace;
cout<<x<<y<<endl;
}
{
using namespace Other;
cout<<x<<y<<endl;
}
{
MySpace::x = 100;
Other::y = 200;
cout<<MySpace::x<<Other::y<<endl;
}
return 0;
}
可以使用块语句将命名空间限定在块语句内部。
D、支持嵌套
namespace MySpace
{
int x = 1;
int y = 2;
namespace Other {
int m = 3;
int n = 4;
}
}
E、协作开发
在实际项目开发中,可以将一个类或者具有相同属性的多个类声明在一个命名空间内,在使用时只需要声明命名空间即可。
#ifndef A_H
#define A_H
namespace XX {
class A
{
public:
A();
~A();
};
}
#endif // A_H
#include "a.h"
using namespace XXX
{
A::A()
{}
A::~A()
{}
}
十一、系统string类
除了使用字符数组来处理字符串以外,C++引入了字符串类型。可以定义字符串变量。
1、定义和初始化
string str;
str = "china";
string str2 = " is great ";
string str3 = str2;
2、类型大小
cout<<sizeof(string)<<" "<<str.max_size()<<str.size()<<endl;
3、运算
A、赋值
string str3 = str2;
B、加法
string combine = str + str2;
C、关系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;
4、string类型数组
string数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用 string 数组存储,字符串数组的话,效率即高又灵活。
string sArray[10] = {
"0",
"1",
"22",
"333",
"4444",
"55555",
"666666",
"7777777",
"88888888",
"999999999",
};
for(int i=0; i<10; i++)
{
cout<<sArray[i]<<endl;
}
5、string类的成员函数
int capacity()const; //返回当前容量(即string中不必增加内存即可存放的元素个数)
int max_size()const; //返回string对象中可存放的最大字符串的长度
int size()const; //返回当前字符串的大小
int length()const; //返回当前字符串的长度
bool empty()const; //当前字符串是否为空
void resize(int len,char c);//把字符串当前大小置为len,并用字符c填充不足的部分
十二、C语言开发人员的建议
1、在 C++中几乎不需要用宏,用const或enum定义明显的常量,用inline避免函数调用的额外开销,用模板去刻画一族函数或类型,用namespace去避免命名冲突。
2、不要在你需要变量之前去声明,以保证你能立即对它进行初始化。
3、不要用malloc,new运算会做的更好
4、避免使用 void*、指针算术、联合和强制,大多数情况下,强制都是设计错误的指示器。
5、尽量少用数组和C风格的字符串,标准库中的string和vector可以简化程序
6、更加重要的是,试着将程序考虑为一组由类和对象表示的相互作用的概念,而不是一堆数据结构和一些可以拨弄的二进制。