C++在面向过程上的扩充全总结(๓˙ϖ˙๓)——总览
先介绍一下我们的C++:
C++是C语言的继承,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行以继承和多态为特点的面向对象的程序设计。C++擅长面向对象程序设计的同时,还可以进行基于过程的程序设计,因而C++就适应的问题规模而论,大小由之。
C++不仅拥有计算机高效运行的实用性特征,同时还致力于提高大规模程序的编程质量与程序设计语言的问题描述能力。
也就是说C++既可以拥有像C一样面向过程的程序设计,也可以拥有C所不具备的面向对象的程序设计,显而易见地,C++是一个功能强大的混合型的高级语言。既然是C的超集,那么我们今天就理一理它在面向过程上对C有着哪些扩充。
1.cin与cout的引入
cin是C++的输入风格,cout是C++的输出风格。二者是通过流的方式实现的。
cin使用的是流提取运算符,即>>,其作用是将输入终端的输入流中提取若干字节送到计算机中指定的变量。
cout使用的是流插入运算符,即<<,其作用是将需要输出的内容插入到输出流中,送到输出终端中进行显示。
流指的是来自设备或传给设备的一个数据流。数据流是由一系列字节组成的,这些字节是按进入流的顺序排列的。
2.标准输入流和标准输出流的控制符
在输出浮点数时,C++默认的流输出数值有效位是6,所以不管数据是多少都只输出六位有效数字。可以采用setprecision(n)来控制输出流显示浮点数的数字个数。
I/O流常用控制符:
使用控制符时,需要加入头文件#include<iomanip>。C++有两种方法控制格式输出:1、用格式控制符;2、用流对象的成员函数。
使用格式控制符:
控制符 作用
dec 设置整数的基数为10
hex 设置整数的基数为16
oct 设置整数的基数为8
setbase(n) 设置整数的基数为n,其中n的取值为8,10,16
setfill(c) 设置填充字符,c可以是字符常量或字符变量
setprecision(n) 设置实数的精度为n位。在以一般十进制形式输出时,n代表有效数字。在以fixed(固定小数位)形式和scientific(指数)形式输出时,n为小数位数。
setw(n) 设置字段宽度
setiosflags(ios::fixed) 设置浮点数以固定的小数位数显示。
setiosflags(ios::scientific) 设置浮点数以科学计数法(即指数形式)显示。
setiosflags(ios::left) 输出数据左对齐。
setiosflags(ios::right) 输出数据右对齐。
setiosflags(ios::shipws) 忽略前导的空格。
setiosflags(ios::uppercase) 在以科学计数法输出E和十六进制输出字母X时,以大写表示。
setiosflags(ios::showpos) 输出正数时,给出“+”号
resetiosflags 终止已设置的输出格式状态,在括号中应指定内容。
成员函数:
流成员函数 与之作用相同的控制符 作用
precision(n) setprecision(n) 设置实 数的精度为n位。
width(n) setw(n) 设置字段宽度为n位。
fill(c) setfill(c) 设置填充字符c。
setf( ) setiosflags( ) 设置输出格式状态,括号中应给出格式状态,内容与控制符setiosflags括号中内容相同。
ubsetf( ) resetiosflags( ) 终止已设置的输出格式状态。
dec 置基数为10 相当于"%d";hex 置基数为16 相当于"%X";oct 置基数为8 相当于"%o"。
设置格式状态的格式标志:
格式标志 作用
ios::left 输出数据在本域宽范 围内左对齐
ios::right 输出数据在本域宽范围内右对齐
ios::internal 数值的符号位在域宽内左对齐,数值右对齐,中间由填充字符填充
ios::dec 设置整数的基数为10
ios::oct 设置整数的基数为8
ios::hex 设置整数的基数为16
ios::showbase 强制输出整数的基数(八进制以0打头,十六进制以0x打头)
ios::showpoint 强制输出浮点数的小点和尾数0
ios::uppercase 在以科学计数法输出E和十六进制输出字母X时,以大写表示
ios::showpos 输出正数时,给出“+”号。
ios::scientific 设置浮点数以科学计数法(即指数形式)显示
ios::fixed 设置浮点数以固定的小数位数显示
ios::unitbuf 每次输出后刷新所有流
ios::stdio 每次输出后清除 stdout,stderr
3.逻辑变量和逻辑常量的引入
在C++中使用bool用来定义一个逻辑变量,如bool flag=true bool m=false,其中,true即两个逻辑常量的一个,用数值1来表示,false是另一个逻辑常量,用数值0来表示。值得说明的是,bool定义的逻辑变量的取值只能是0或者1,并且在内存中只占1字节。
false和true虽然是两个英文单词,但是在计算机存储中仍旧是以0和1来进行存储的,并不是两个字符串,而是两个整形变量,可以与其他整形变量进行运算。特殊的是,这种“整形变量”的并不是占4个字节,而是特殊的1字节。
4.内联函数
内联函数用inline进行声明,其本质就是将调用函数的代码复制粘贴进main函数中。那么有人会问了,为什么要这么做?老老实实地调用函数不好吗?
某些时候还真不好。例如:
int MAX(int a,int b)
{
if(b>a)
{
a=b;
}
return (a);
}
这种很简单的取较大值的函数很常见,如果一个main函数里需要调用几次甚至十几次这个MAX,那肯定是使用内联函数更好。直接将MAX代码内嵌进main函数中,避免了频繁的函数调用,让程序具有更高的执行效率。另外说一下,inline直接在函数声明中出现就好,在函数定义的时候就不要出现了。
那么所有的函数都可以直接内嵌吗?答案是否定的,如下:
inline void sort(char(*p)[20]);
void sort(char(*p)[20])
{
int i,j;
char t[20];
for(j=1;j<5;j++)
{
for(i=0;i<5-j;i++)
{
if(strcmp(p[i],p[i+1])>0)
{
strcpy(t,p[i]);
strcpy(p[i],p[i+1]);
strcpy(p[i+1],t);
}
}
}
}
int main(void)
{
char s[5][20];
char(*p)[20];
int i;
for(i=0;i<5;i++)
{
cin>>s[i];
}
p=s;
cout<<endl;
sort(p);
for(i=0;i<5;i++)
{
cout<<s[i]<<endl;
}
return 0;
}
这就是一个很好的例子。数组指针对字符串排序,需要两层for循环还需要临时变量,那么把这些全都内嵌main函数,将main函数源代码拉得无限长肯定是不合适的,这让目标程序变长,目标文件变大。但是如果这么声明inline也没什么问题,聪明的编译系统会选择性地将过于复杂的子函数从内联函数中排除,把它当做一个普通的子函数进行调用,这样就不会直接内嵌进main中了。所以内联函数最好要求只用if-else语句,其他的语句不要有,总语句少于5句是最好,编译系统一看子函数比较简单就会把它放入到内联函数的名单中,在编译时直接内嵌,让代码执行效率更高。内联函数只适用于规模较小、语句简单不复杂并且需要高频率调用的子函数。
5.函数重载
一句话,一物多用。
int MAX(int a,int b)
{
if(b>a)
{
a=b;
}
return (a);
}
double MAX(double a,double b,double c)
{
if(b>a)
{
a=b;
}
if(c>a)
{
a=c;
}
return (a);
}
int main(void)
{
int x,y;
cout<<"请分别输入x y的值:\n";
cin>>x>>y;
int p=MAX(x,y);
cout<<p<<endl;
double a,b,c;
cout<<"请输入a b c 的值:\n";
cin>>a>>b>>c;
double d=MAX(a,b,c);
cout<<d<<endl;
return 0;
}
具体表现如上所示,同一个函数名可以用于函数返回值不同、参数类型不同甚至是参数个数也不同的两个函数,连函数体都并不完全一样,这就是函数重载。重载函数的参数个数、参数类型或者参数顺序三者中必须且至少有一种不相同,函数的返回值可以相同也可以不同,函数体也可以相同也可以不相同,但是有一点还是要说一下,不要偏得太离谱就行₍˄ุ.͡˳̫.˄ุ₎ฅ,最好两个函数的功能相近些,这样程序可读性会更高,一个函数排序一个函数求圆柱体体积确实太憨了显得…
6.函数模板
template<typename T>
T MAX(T a,T b)
{
if(b>a)
{
a=b;
}
return (a);
}
int main(void)
{
int x,y;
cout<<"请分别输入x和y:\n";
cin>>x>>y;
int better=MAX(x,y);
cout<<"两者较大的是"<<better<<endl;
double a,b;
cout<<"请分别输入a和b:\n";
cin>>a>>b;
double q=MAX(a,b);
cout<<"两者较大的是"<<q<<endl;
return 0;
}
如上所示,只留一个函数名用于调用函数。T即是虚拟类型名,可以用来表示任何类型:int、long int、float、char…然后就可以利用这个模板在main中进行调用。函数的参数类型可以完全不同,参数的顺序也可以不同,但是函数体必须完全相同且函数的参数个数必须完全相同。
这里的typename可以用class代替,二者都表示函数名。而typename后面的T也可以用别的R、ER、pppp等等其他标识符来表示,只是许多人习惯了用T这个大写字母来表示。
在之前的C++程序中都普遍用class,typename是前不久在被加入到标准C++中的,那么为什么现在更多的人都用typename了呢,是因为class有类的概念,但是这里的class和那个面向对象的类可是没有一点点关系的。为了防止混淆概念所以才引入的typename,意如其名,类型名称。
7.带有默认参数的函数
int MAX(int a,int b,int c=100);
int MAX(int a,int b,int c)
{
if(b>a)
{
a=b;
}
if(c>a)
{
a=c;
}
return (a);
}
int main(void)
{
int x,y;
cout<<"请分别输入x y:\n";
cin>>x>>y;
int z=MAX(x,y);
cout<<"最大的数是:\n";
cout<<z<<endl;
}
将如上代码运行,只要输入的数不超过100,那么输出的最大值就是100,相当于已经把一个实参为100的参数提前传入到了形参。除非实参有三个,相当于将声明的int c=100给替换了,这样带有默认参数的函数相当于就被抹去了。
一个函数不可以既是重载函数又是带有默认参数的函数。因为当调用函数时少写了一个参数,编译系统无法判定到底是函数重载了还是带有了默认参数,这就出现了二义性,会使程序挂掉。
另外强调一下,带有默认参数的函数只要在声明中出现默认参数就好,不要在定义时也出现默认参数了。这样做的目的就是防止默认参数的出现差异,如果声明给出默认参数是100,定义时给出的默认参数是10,那么不同的编译系统对此会有不同的两种判断:
1.报错,默认参数不匹配
2.不报错,以声明时给出的默认参数为准,因为声明在先,定义在后,以第一次的为准。
所以为了防止出现类似情况,只在声明中给出默认参数就好。
8.Const指针
其实这个在C语言的时候已经存在了,拿到这里专门说一下,因为在C++中,const指针会被经常使用到。
如下,有两个变量。
int main(void)
{
int a=1,b=2;
return 0;
}
① 常指针(指针常量)
int main(void)
{
int a=1,b=2;
int*const p=&a;
return 0;
}
此时,这个int*类型指针就是一个常指针,即指向的方向不可变,但是指向的内容可以变。
*p=2 合法,但是p=&b不合法
②指向常量的指针(常量指针)
int main(void)
{
int a=1,b=2;
const int*p=&a;
return 0;
}
此时,这个int*类型指针就是一个常量指针,即指向的内容不可变,指向的方向可以变。
*p=2不合法,p=&b合法
在这里需要说明一下,*p确实没办法改变,也就是间接访问的方式访问这个a变量无法改变它的值,可是我们可以直接修改,如a=10,则直接修改掉了a值。这里所说的无法修改指的是通过指针的方式访问这个变量时(间接访问)无法修改a值,直接访问还是没有任何问题的,当然,修改过a的值后,*p的值也改变了。同时,由于p=&b合法,在p指向b时,*p的值不再是1,而是2。
③指向常量的常指针
合二为一:
const int*const p=&a;
这个int*类型指针指向的方向不可以改变,指向的内容也不可以改变,在此不再赘述。
9.引用
引用是C++对C语言的重要扩充。引用就是某一变量(目标)的一个别名,对引用的操作与对变量直接操作完全一样。引用的声明方法:类型标识符 &引用名=目标变量名。
引用作为函数参数是C++扩充引用的主要目的。如果形参是“引用”,那么实参传递给形参的过程会被编译系统自动识别为地址传递,注意,这里是真正意义上的地址传递,不是C中的单向的值传递。
引用中,实参为两个普通的变量,形参是“引用”,编译系统会自动识别将两个变量的地址传给形参。此时,形参中的地址即为实参中的地址,这个过程是真正意义上的地址传递。
之前有些问题只能用指针来解决的,现在有了引用就可以多了一种思路,并且引用在面向对象的程序设计中也有很大的作用,这个我们以后再详细说明。
10.new and delete && malloc and free
new和delete是C++新扩充的运算符。
一、基本概念
malloc/free:
1、函数原型及说明:
void *malloc(long NumBytes):该函数分配了NumBytes个字节,并返回了指向这块内存的指针。如果分配失败,则返回一个空指针(NULL)。
void free(void *FirstByte): 该函数是将之前用malloc分配的空间还给程序或者是操作系统,也就是释放了这块内存,让它重新得到自由。
2.内存操作:
malloc函数的参数是接受需要分配的内存字节数,如果内存能够满足请求量,那么将会返回:指向被分配的内存块起始位置
free函数释放的是指针指向的内存(不是释放的指针本身,不会删除指针本身),其中指针必须指向所释放内存空间的首地址
new/delete:
1.操作时发生事件:
new的时候会有两个事件发生:1).内存被分配(通过operator new 函数) 2).为被分配的内存调用一个或多个构造函数构建对象
delete的时候,也有两件事发生:1).为将被释放的内存调用一个或多个析构函数 2).释放内存(通过operator delete 函数)
2.特殊应用:
使用delete是未加括号,delete便假设删除对象是单一对象。否则便假设删除对象是个数组
因此,如果在调用new时使用了[],则在调用delete时也使用[],如果你在调用new的时候没有[],那么也不应该在调用时使用[]。
二、malloc/free 和new/delete 的本质区别:
1.malloc/free是C/C++语言的标准库函数,new/delete是C++的运算符
2.new能够自动分配空间大小
3.对于用户自定义的对 象而言,用maloc/free无法满足动态管理对象的要求。对象在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于malloc /free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数和析构函数的任务强加于malloc/free。因此C++需要一个能对 对象完成动态内存分配和初始化工作的运算符new,以及一个能对对象完成清理与释放内存工作的运算符delete---简而言之 new/delete能进行对对象进行构造和析构函数的调用进而对内存进行更加详细的工作,而malloc/free不能。
三、联系
既然 new/delete的功能完全覆盖了malloc/free,为什么C++还保留malloc/free呢?因为C++程序经常要调用C函数,而C程序 只能用malloc/free管理动态内存。如果用free释放“new创建的动态对象”,那么该对象因无法执行析构函数而可能导致程序出错。如果用 delete释放“malloc申请的动态内存”,理论上讲程序不会出错,但是该程序的可读性很差。所以new/delete,malloc/free必 须配对使用。
四、使用范例
void * malloc(size_t size);
用malloc 申请一块长度为length 的整数类型的内存,程序如下:
int *p = (int *) malloc(sizeof(int) * length);
我们应当把注意力集中在两个要素上:“类型转换”和“sizeof”。
1. malloc 返回值的类型是void *,所以在调用malloc 时要显式地进行类型转换,将void * 转换成所需要的指针类型。
2.malloc 函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
void free( void * memblock );
为什么free 函数不象malloc 函数那样复杂呢?这是因为指针p 的类型以及它所指的内存的容量事先都是知道的,语句free(p)能正确地释放内存。如果p 是NULL 指针,那么free
对p 无论操作多少次都不会出问题。如果p 不是NULL 指针,那么free 对p连续操作两次就会导致程序运行错误。
new/delete 的使用要点
运算符new 使用起来要比函数malloc 简单得多,例如:
int *p1 = (int *)malloc(sizeof(int) * length);
int *p2 = new int[length];
这是因为new 内置了sizeof、类型转换和类型安全检查功能。对于非内部数据类型的对象而言,new 在创建动态对象的同时完成了初始化工作。如果对象有多个构造函数,那么new 的语句也可以有多种形式。
如果用new 创建对象数组,那么只能使用对象的无参数构造函数。例如
Obj *objects = new Obj[100]; // 创建100 个动态对象 不能写成
Obj *objects = new Obj[100](1);// 创建100 个动态对象的同时赋初值1
在用delete 释放对象数组时,留意不要丢了符号‘[]’。例如
delete []objects; // 正确的用法
delete objects; // 错误的用法 后者相当于delete objects[0],漏掉了另外99 个对象。
到这里就算是彻底整理好啦 (~ ̄▽ ̄)~
可以说是囊括了所有C++在C上面基于过程的扩充点<( ̄︶ ̄)↗
黑发不知勤学早,白首方悔读书迟,让我们继续在0101的世界里努力前行!如有问题欢迎大家指正。