C++相对于C的扩展:
一.类型增强
1. 类型检查更严格
左值右值的类型一致
2. 新增bool类型
C语言用0,1表示真假,在C++中使用bool类型。
bool类型相当于一个枚举
enum bool
{ false,true};
本质还是一样的
3. 真正的枚举
C语言中的枚举本质是整形,枚举变量可以用任意整形赋值,C++中只能使用在枚举体中初始化的元素
4. 表达式的值可被赋值
C语言中
int a,b = 5;
(a = b) = 10; //仅C++可用
//printf(”a = %d,b = %d“,a,b);
//cout << a << b <<endl;
是不可使用的,即表达式不可作为左值
在C++中成立,表达式的值可以被赋值(注意赋值对象是表达式的左值a,即结果是a = 10 , b 依旧等于5)。
C++中:
尽量不使用指针
尽量不进行强制类型转换
尽量不要用字符数组,使用string类代替更安全
//char name[10];
//cin >>name;
string name;
cin >> name;
二.输入输出:
标准输入输出流:
cin cout
格式化输出:
cout默认输出6位有效数据
setw() 设置域宽
**setprecision()**设置有效位数
**setprecision(n)<<setiosflags(ios::fixed)**合用,可以设置小数点右边的位数
**setiosflags(ios::left)**设置左对齐
setfill(‘0’) 设置填充符
按进制输出:
int i = 123;
cout<<i<<endl; //默认10进制输出
cout<<dec<<i<<endl; //10进制输出
cout<<hex<<i<<endl; //16进制输出
cout<<oct<<i<<endl; //8进制输出
cout<<setbase(16)<<i<<endl; //转为16进制输出
三.函数重载
C++支持同名函数
int func(int a)
{
}
int func(char a)
{
}
重名函数的要求:
- 参数个数,类型,顺序至少有一个不同则构成重载
- 返回类型不同不构成重载(意思是跟返回类型没关系)
重载函数在调用时的匹配原则:
- 根据参数类型,个数严格匹配,找到则调用
- 没有严格匹配的就依据隐式转换寻求匹配
C++允许int到long,double;double到int,float的隐式类型转换
重载的底层实现原理:
name mangling技术(倾轧),对重名函数根据不同参数类型进行改名从而区分
extern"C"
倾轧发生在两个阶段,函数定义时(.cpp编译阶段)和函数声明使用时(.h声明阶段),只有两个阶段同时倾轧或者都不倾轧,才能匹配
extern “C” 可以避免函数倾轧
C++兼容C类库,.c文件的类库中函数没有倾轧,但是c++包含.h文件要被倾轧,就可以通过extern"C"来避免.h文件中的函数声明被倾轧。
这种说法比上面更好理解一点:
C++支持函数名重载,而C不支持,因此C++使用C库时,(用C++的规则编译C库)编译器生成目标文件时,函数名在目标文件中的临时内部名称规则不同。导致链接时符号对不上;而使用extern “C” 把声明的函数括起来,让其以C的规则编译,就对的上了。
举例sting.h文件
extern "C" {
char * __cdecl _strset(char *_Str,int _Val) __MINGW_ATTRIB_DEPRECATED_S
EC_WARN;
char * __cdecl _strset_l(char *_Str,int _Val,_locale_t _Locale) __MINGW
_ATTRIB_DEPRECATED_SEC_WARN;
char * __cdecl strcpy(char * __restrict__ _Dest,const char * __restrict
__ _Source);
}
操作符重载:
C++提供了运算符重载机制。可以为自定义数据类型重载运算符。实现构造数据类型也可以像基本数据类型一样的运算特性
using namespace std;
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 = c1+c2; //相当于调用函数operator+(c1,c2);
cout<<sum.real<<" "<<sum.image<<endl;
return 0;
}
重载了一个操作符+ ,可以实现结构体类型相加,其本质是函数的调用
四.默认参数:
C++支持函数定义时赋值(默认参数),调用时不传进参数就使用默认值
int function(int a = 1 ,int b = 2)
{
}
int main()
{
function(); //a = 1;b = 2
function(3);//a = 3,b = 2
}
默认参数的规则:
- 默认的顺序从右到左,不能跳跃(猜测因为传参是从左向右匹配的)
- 函数默认值和重载是冲突的,一个函数不能既作重载由有默认参数,编译器不能判断(判断重载是用参数作为依据的,如果使用默认参数就没法根据类型判断)
- 函数声明和定义一体时,默认参数在定义(声明)处,声明在定义前时,默认参数在声明处
五.引用:
引用是为已有的对象起个别名,对引用的所有操作都是在与之绑定的对象上进行的
int main()
{
int a;
int &b = a;
}
规则:
- 引用是对已有变量的声明,类型和地址与原变量一致,不分配内存
- 声明的时候必须初始化,一旦声明不可变更
- 引用时&符号作左值,作右值时作取地址符
- 可对引用再次引用,表示一个变量有多个别名
int a;
int &ra = a;
int &rra = ra; //表示a有两个别名ra 和rra
const:
- const对象的引用必须是const的,不能讲普通引用绑定到const对象
const int a = 1;
int &ra = a;//error
- const引用可以使 相关类型对象(常量,非同类型变量或表达式)初始化
常量:
const int &r = 2; //对常量的引用,合法
不同类型变量:
int a = 10
const double &rb = a; //合法
实际上相当于创建了临时变量,
double tmp = a;
const double &rb = tmp;
尽量使用const (能用就用) :
- 使用const可以避免无意修改数据
- const可以处理const 和非const 实参,否则只能接受非const实参
- 使用const引用,可使实参和引用参数不匹配时能够正确生成并使用临时变量
C++中 const 定义的变量称为常变量。变量的形式,常量的作用,用作常量,常用于取代#define 宏常量。
链接:指针和引用的不同之处
六.new/delete
兼容malloc/free,new/delete是关键字而非函数
new用法:
int *p = new int ;//开辟大小为sizeof(int)的空间
int *a = new int(5);//开辟大小为sizeof(int)的空间,并初始化为5
int *b = new int[100];//开辟大小为100的整形数组空间
int **p = new *int [5];//开辟大小为5的整形指针数组空间
int (*a)[6] = new int[5][6];//开二维数组空间
delete用法:
int *a = new int ;
delete a;//释放单个int空间
int *a = new int[5];
delete []a;//释放int数组空间
申请内存的返回值:
C中通过判断返回的指针是否为NULL来判断是否申请成功,
C++中内存申请失败会抛出异常,但一般不使用,还是自己判断
int *q = new (std::nothrow)int[10];
if(q == NULL)
return -1;
注意事项;
- new/delete比maloc/free效率更高
- 配对使用不能错
- new完一定不能忘记delete,避免内存泄露
七.内联函数inline
用法:
inline int func()
{
return 0;
}
C及C++中的三种函数:
宏函数:
优:预处理时内嵌展开,避免函数调用开销
缺:易产生歧义;不会被编译器检查类型;被多次调用时多次内嵌易使text段体积增大
函数:
优:编译器会进行类型检查;不易产生歧义;不会多次内嵌,text段体积小;
缺:函数调用时压栈出栈,调用开销较大
内联函数:
综合上面两种优点,进行类型检查,又原地内嵌展开,避免调用开销
但只适合频繁调用,体积小的函数
inline只是对编译器的建议,如果函数行数较多还加inline,编译器会无视(register 修饰变量 类似)。
八.强制类型转换
静态类型转换:static_cast<目标类型>(标识符)
规则:可以隐式转换,则就可以静态转换
int *p ;
void *q;
p = static_cast<int *>(q);
q = static_cast<void *>(p);
强制转换可能会丢失精度,需要程序员自己判断。
重解释类型转换:reinterpret_case<目标类型>(标识符)
不可以隐式类型转换的,则需要重解释类型转换
脱常类型转换:const_cast<目标类型>(标识符)
目标类型只能是指针或引用
例1:
const int x = 200;
//int &a = x;//error,引用类型和变量类型不一致
int & a =const_cast<int&>(x);//脱掉了const
例2:
const int d = 1;
int *pd = const_cast<int *>(&d);
*pd = 2;
cout << "d = "<< d<<endl;
cout << "*pd = "<< *pd <<endl;
cout <<"&d = "<<&d<<endl;
cout << "pd = "<< pd<<endl;
例2结果:
d = 1 //d没有被改变
*pd = 2 //
&d = 0x61fe7c
pd = 0x61fe7c //d地址和pd相同
例2说明const修饰的变量即使脱常了也不能通过指针被改变(好像宏一样)
使用 const_cast 去除 const 限定的目的不是为了修改它的内容,使用 const_cast 去除 const 限定,通常是为了函数能够接受这个实际参数。
动态类型转换:dynamic_cast<目标类型>(标识符)
用于多态中的父子类之间的强制转化
不是在编译阶段进行,是在运行时进行,运行时才知道转换结果是NULL还是有效对象。
C++中尽量使用这四种方式进行转换,而不要用C中的隐式和强制类型转换。
九.命名空间namespace
为了大型项目开发,而引入的一种避免命名冲突的一种机制,是对全局空间的再次划分
声明:
namespace SPACE
{
全局变量 int a;
数据类型 struct Stu{};
函数 void func();
其它命名空间 namespace
}
使用方法:
- 第一种:直接指定命名空间
SPACE::a = 5
这是标准用法,但是很麻烦
- 第二种:using + 命名空间 + 空间元素
using SPACE a;
a = 5;
用的少
- 第三种:using +namespace + 命名空间
using namespace SPACE;
a = 5;
如果不同命名空间内容不冲突,这种用法较多,出问题再用第一种
同名的命名空间自动合并,合作开发基于此
命名空间支持嵌套
匿名命名空间
namespace 后面没有名字
namespace
{
void func()
{
}
}
匿名命名空间的意义:
- 作用类似于全局变量和函数前加static ,即该匿名命名空间内的变量或函数只能在本文件中使用(比static使用范围广)
- 匿名命名空间的逻辑符合基本命名空间的一般原则
- 多文件时使用,只有单文件时没必要使用
十.系统string类
C++使用string类处理字符串变量
定义及初始化
string str = "klyer";
cout<< str<<endl;
//string str;
//str = "klyer";
类型大小
string str = "klyer";
cout<<sizeof(str)<<endl;
cout<<sizeof(string)<<endl;
输出24??
常用运算
- 赋值
string str1 = str2;//将str2值赋给str1
这里string不能少
- 加法
string str3;
str3 = str1 +str2; //str2续在str1后面
cout<<str3<<endl;
- 判断关系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;
成员操作函数
- 下标操作
string str = "klyer";
str[3] = 'w';
cout<<str<<endl; //输出klywr
- 求字符串大小
int size();
string str = "klyer";
cout<<str.size()<<endl; //输出为5
- 返回C串
为了C中的字符数组使用的
char * c_str(); //返回char *型
string str = "klyer";
char buffer[10];
strcpy(buffer,str.c_str());
cout << buf << endl;
- 查找
int find(char c, int pos = 0);//pos位开始查找 c,返回下标值,没有返回-1
- 删除
string &erase(int idx=0,int n = npos);
string str1 = "klyer";
string str2;//或者 string &str2 = str1;
str2= str1.erase(0,2); //从0开始删除往后数n位的字符串
cout <<str2<<endl;//输出 yer
- 交换
void swap(string &s2);
string str1 = "klyer";
string str2 = "1234";
str1.swap(str2); //和交换str2
cout<<str1<<endl; //输出 1234
- string 类型数组
string array[10]=
{
"0","12","234",
};
使用二维数组存字符串,列数由最长字符串决定,浪费空间;
使用二级指针可以解决浪费内存问题,但是操作麻烦;
string类型数组存放字符串高效又灵活;