1、C++的输入输出
C++的输入输出在保留C语言的输入输出系统之外,还利用继承的机制创建出一套自己的方便、一致、安全、可扩充的输入输出系统,即C++输入输出(I/O)流类库。
有了“流”的思想,对所有的输入输出就都是一样的了,输入时,字节流从输入设备流向内存,输入设备可以是键盘、磁盘、光盘等,使用时创建输入设备的输入流对象,然后通过>>运算符将数据从输入流对象读入内存的变量或其他数据结构中,输出时,字节流从内存流向输出设备,包括显示器、磁盘或其他输出设备,使用时创建输出设备的输出流对象,然后通过<<运算符讲数据从内存输出到输出流对象,完成输出操作。
2、C++对C语言数据类型的扩展
基本类型增加了布尔型,构造类型增加了类类型,此外还增加了引用类型。
细节上的应用区别
1)常变量
为了使常量 也能像变量那样进行类型检查,C++提供了用const定义常变量的方法,如:const float PI =3.14159; float const PI = 3.14159;
PI 前面有一个关键字const,就表示PI是一个常变量,定义PI时必须对PI进行初始化,之后PI的值就不可能再改变了,任何修改PI或者给PI赋值的语句都是非法的,程序不能通过编译。也可以使用表达式对常变量进行初始化,系统会先计算表达式的值,然后再将值赋给常变量。另,系统在静态存储区给常变量分配内存单元,而普通变量是在动态存储区分配内存单元的。
2) 指针
空指针 int *p = NULL;(或 0)
野指针 int *p;(未初始化的指针)
一般 都在定义指针时将指针初始化为空指针。程序中要彻底杜绝使用野指针。一旦指针被free,不再使用时,及时置为NULL。
指针与const
const 与 * 之间的相对位置关系有两个,可以形成三种指针
只在*之前有const的指针,称为指向const变量的指针
1)如果一个变量已经被声明为const常量,则只能用指向const变量的指针去指向它, 而不能用一般的指针(指向非const型变量的指针)去指向他。
2)指向常变量的指针也可以指向普通变量,此时,可以通过指针访问该变量,但是不能通过指针改变该变量的值,但是可以通过变量本身修改值。
3)指向const变量的指针本身可以修改,即可以指向其他变量。
4)两种形式:const int *p = NULL ; int const *p = NULL;
/** 指向常变量的指针 **/
#include<iostream>
using namespace std;
int main()
{
const int *p = NULL;
int const *pp = NULL;//也可以这么定义
const int a = 0;
int const c = 10;
int b = 15;
p = &a;
cout<<"*p="<<*p<<endl;
p = &b; //可以更换p指的单元
cout<<"*p="<<*p<<endl;
// *p = 200; //错误,不能通过指针p修改p所指向单元内容
b=200; //可以通过变量本身修改值
cout<<"*p="<<*p<<endl; //同步更新修改后的值
return 0;
}
指向const变量的指针最常用于函数的形参,目的是保护形参指针所指定向的实参变量,使他在函数的执行过程中不被修改,在函数调用时,其对应的实参既可以是指向const变量的指针,也可以是指向非const变量的指针。例如,字符串处理的函数一般都是这样处理的。
char *strncat(char *s1, const char *s2, size_t n); //s1是需要修改的,s2不需要修改,最好不要修改,用const 保护
int strcmp(const char *s1, const char *s2); //函数功能仅仅是比较,两个字符串都不需要修改,用const保护
另,,采用这种方法,还可兼容当上述字符串参数为字符串常量时的情况。
如 strcmp("hello","world");
"hello","world"会产生一个临时变量,在C++中,这些临时变量都是const类型的。
只在*之后又const的指针,称为const指针
指针变量的值不能改变,定义时就必须初始化,但可以通过指针修改所指向单元的内容,不能更换指向的单元
*前后都有const的指针,称为指向const变量的const指针
既不可以通过指针修改所指向单元的内容,又不能修改指针指向的单元
void指针 无类型指针,可以指向任意类型的数据,但是在使用前必须进行强制类型转换
void *memcpy(void dest,const void*src,size_t len);
3、内存管理 new和delete运算符
C语言中使用 malloc 和free函数 需要包含头文件 stdlib.h 或 alloc.h
void *malloc(int size) 和 void free(void * block)
C++ 新增new 和 delete
int *p=NULL;
p = new int;也可以初始化,p = new int(100);
delete p;
p=NULL;
数组
int *p = new int[100];
int *p = new int[n];n是变量
delete[] p;
上述使用必须配对出现
4、引用
引用就是某一变量的别名,对引用的操作与对该变量直接操作完全一样,引用的声明方式: 类型标识符 & 引用名 = 目标变量名;
举例 :
int x = 100;
int &rx = x;
#include<iostream>
using namespace std;
int main()
{
int x=100;
int &rx=x;
cout<<"rx="<<rx<<endl;
rx = 200;
cout<<"x="<<x<<endl;
return 0;
}
程序运行结果:
rx=100
x=200
声明引用时,引用前面的类型标识符是指目标变量的类型,且必须同时对其进行初始化,即声明它代表哪一个变量。引用声明完毕后,相当于目标变量有两个名称,即该目标变量原名称和引用名,且不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,因此引用本身不占存储单元,系统也不给引用分配存储单元。
引用的使用,引用传参
#include<iostream>
using namespace std;
void swap(int &rx,int &ry)
{
int temp = rx;
rx = ry;
ry = temp;
}
int main()
{
int x=100,y=200;
cout<<"before swap:";
cout<<"x="<<x<<","<<"y="<<y<<endl;
swap(x,y);
cout<<"after swap:";
cout<<"x="<<x<<","<<"y="<<y<<endl;
return 0;
}
运行结果:
before swap:x=100,y=200
after swap:x=200,y=100
上述程序中,将形参的数据类型从int型变为int型引用,这种传递参数的方式叫作引用传参。形参rx,ry是两个int型引用,swap函数调用时,swap的rx,ry被初始化为main函数变量x,y的别名,访问swap的rx,ry和main函数的x,y效果完全一样。
这种传递方式书写简单,易于理解,而且可以提高程序的执行效率(不需要复制数据),在许多情况下可以代替指针的操作。
c++提供引用机制,主要是利用它作为函数参数,以扩充函数传递数据的功能。
进一步说明:
1、不能建立void类型的引用,任何实际存在的变量都是属于非void型的。
2、不能建立数组的引用。引用 只能是 变量 或 对象 的引用。数组是具有某种类型的数据的集合,其名字表示该数组的起始地址而不是一个变量。所以不能建立数组的引用。
3、变量的引用也有地址,与被引用的变量共享,将其赋给一个指针,指针指向的是原来的变量。
int a = 3;
int &b = a;
int *p = &b;
p 相当于指向 a;
4、可以建立指针变量的引用,指针变量也是变量。
int a = 3;
int *p = &a;
int * &rp = p;// rp是一个指向 int型变量 的 指针变量p 的 引用
引用不是独立的数据类型, 语句 int & *p=&a; 是错误的。
5、 常引用
const 类型标识符 & 引用名 = 目标变量名;
用这种方式声明的引用,不能通过引用对目标变量的值进行修改,从而使引用的目标成为const,达到引用的安全性
int a = 3;
const int &ra = a;
ra = 1; //错误,不能通过引用名修改
a = 1; //正确
常引用 用作函数的形参
void Show(const string & s)
{
s = "hello word!";//错误,不能正常修改引用形参的值
cout<<s<<endl;
}
利用常引用作为函数形参,既能提高程序的执行效率,又能
保护传递给函数的数据不在函数中被改变,达到保护实参的目的。
还有兼容性问题的举例
举例:
string StrFunc();
void Show(string &s);
则下面的表达式将是非法的:
Show(StrFunc());
Show("hello world");
原因在于StrFunc()和"hello world"串都会产生一个临时变量,而在c++中,这些临时变量都是const类型的。因此上面的表达式都是试图将一个const类型的变量转换为非const类型,这是非法的。
应该 改成 void Show( const string &s);
因此,引用型形参应该在能被定义成const的情况下,尽量定义成const(在只能使用,不能修改的情况的下,例如swap函数就不符合这类情况),这样,行数调用时的实参既可以是const型,也可以使非const型,但是这样,对于形参来说,就只能使用,不能修改了。
6、可以用常量或表达式(临时变量,也即常量)对引用进行初始化,但此时必须用const进行声明。
例如
int a=3;
const int &b = a+3;
边柜系统将 const int &b = a+3; 转换为:
int temp = a+3;
const int &b = temp;
但是临时变量是内部实现的,用户无法访问临时变量。
还有一种情况,用不同类型的变量对之初始化。
double d = 3.14159;
const int &a = d;
编译系统将“const int &a = d;”转换为:
int temp = d;
const int &a = temp;
因此,
引用a其实是临时变量temp的引用,而非d的引用;在该例子中,必须用const。
7、引用作为函数的返回值
函数的返回值为引用表示该函数的返回值是一个内存变量的别名,可以将函数的调用作为一个变量来使用,可以为其赋值。但是不能返回函数内部局部变量的引用,函数返回时,内部已经销毁了。
#include<iostream>
using namespace std;
int &Max(int &x,int &y)
{
return (x>y)? x:y;
}
int main()
{
int a=2,b=3;
cout<<"a="<<a<<",b="<<b<<endl;
Max(a,b)=4;
//由于函数的返回值为引用,是值较大的那个变量的引用,所以可以为函数赋值
//为函数赋的值实际赋给了两个参数中的较大者,所以a的值为2,b的值为4;
cout<<"a="<<a<<",b="<<b<<endl;
return 0;
}
运行结果如下:
a=2,b=3
a=2,b=4
定义返回引用的函数时,注意不要返回对该函数内的自动变量的引用。否则,因为自动变量的生存期仅局限于函数的内部,当函数返回时,自动变量就消失了,函数就会返回一个无效的引用。函数返回的引用是对某一个函数参数的引用,而且这个参数本身也是引用类型,这样才能保证函数返回的引用有意义。
指针与引用的区别:
(1) 从内存分配上看:指针变量需要分配内存区域,而引用不需要分配内存区域。
(2)指针可以多级,而引用只能是一级。
<span style="white-space:pre"> </span>int aa = 2;
int &ra=aa;
int &rb=ra;
rb=100;
cout<<aa<<endl;
cout<<ra<<endl;
cout<<rb<<endl;
上述,引用ra、rb都是aa的引用,最终运行结果都是100,没有多级的概念。
(3)指针的值可以为NULL,但是引用的值不能为NULL,,并且引用在定义的时候必须初始化。
(4)指针的值在初始化后可以改变,即指向其他的存储单元,而引用在进行初始化后就不能再改变了。
(5)指针和引用的自增(++)运算意义不一样。指针自增是指指针指向下一个内存单元,引用自增是指被引用的变量的值增1;
引用以简略的方式取代了某些条件下指针的作用,尤其适合函数的参数传递。但有些时候,引用还是不能替代指针,这样的情况如下:
(1)如果一个指针所指向的对象,需要分支结构加以确定,或者在途中需要改变它所指向的对象,那么在它初始化之后需要为它赋值,而引用只能再初始化时指定被引用的对象,所以不能胜任。
(2)有时一个指针的值可能是(void 指针),例如当把指针作为函数的参数类型或返回类型时,有时会用void指针表达特定的含义,引用没有类似的用法。
(3)函数指针无法 被引用替代
(4)用new动态创建的对象或数组,需要用指针来存储他的地址
(5)以数组形式传递大批量数据时(数组),需要用指针类型参数
5. 函数
面向过程的C++程序设计具有C语言的函数风格,而在面向对象的C++程序设计中,main函数之外绝大部分的函数被封装到了类中,调用函数一般通过类的对象来调用类里的函数的。
几点特殊的地方
(1)函数默认参数
在定义或声明函数时,给形参一个默认值,如果在调用时没有给该形参传递实参值,则使用默认值作为该形参的值;如果调用时给该形参传递了实参值,则使用实参的值作为该形参的值。
可以使用以下两种声明方式,其中默认参数必须放在右端,最好只在函数原型声明时指定默认值
int Max(int a,int b,int c=0);
int Max(int ,int ,int =0);
有时候会与函数重载出现二义性的问题。
(2)函数与引用的联合应用
在子函数中通过引用传递访问main函数的数组,这样可以方便快捷地传递大量的数据
#include<iostream>
using namespace std;
typedef int arr[8];
int main()
{
void Func(arr &);
int a[8]={1,2,3,4,5,6,7,8};
for(int i=0;i<8;i++)
cout<<a[i]<<" ";
cout<<endl;
Func(a);
for(int i=0;i<8;i++)
cout<<a[i]<<" ";
return 0;
}
void Func(arr &rx)
{
rx[5]=2;
rx[0]=10;
}
运行结果:
1 2 3 4 5 6 7 8
10 2 3 4 5 2 7 8
(3)函数与const
const 修饰函数的参数 void Func(const int *a,const int & b);
const修饰函数的返回值 const T Func(); 或者 const T *Func();
const修饰整个函数 void Func() const; 不能修改数据成员,不能呢调用其他非const成员函数,const对象只能访问const函数
(4)函数重载
同名函数,函数功能类似,只是所处理的数据类型不同。函数调用时编译系统会根据实参的数据类型和个数自动选择合适的Add函数的版本,根据参数表来匹配匹配度最高的。
函数重载需要函数参数的类型或个数必须至少有其中之一不同,函数返回值类型可以相同,也可以不同。单是,不允许参数的个数和类型都相同,而只有返回值类型不同。单是程序的可读性大大下降。
有重载时,为了避免出现二义性,必须完全匹配才行。
#include<iostream>
using namespace std;
int Add(int a,int b);
int Add(float a,float b);
int main()
{
float a=1.0;
int b =1.1;
cout<<Add(a,b)<<endl;//会出现编译错误:[Error] call of overloaded 'Add(float&, int&)' is ambiguous
return 0;
}
int Add(int a,int b)
{
cout<<"add in int"<<endl;
return a+b;
}
int Add(float a,float b)
{
cout<<"add in float"<<endl;
return a+b;
}
当没有函数重载时,参数传递时会发生强制类型转换。
(5)内联函数
系统在编译时将所调用的函数代码直接嵌入到主调函数中,避免了函数调用的开销,适合规模较小(1~5)行而又频繁调用的情况,是一种空间换时间的策略。
#include<iostream>
using namespace std;
inline int Add(int a,int b);
int main()
{
float a=1.0;
int b =1.1;
cout<<Add(a,b)<<endl;
return 0;
}
inline int Add(int a,int b)
{
cout<<"add in int"<<endl;
return a+b;
}
C++ 规定不能作为内联函数的:递归,函数体内包含循环,含有switch,goto语句之类的复杂结构的函数,包含静态数据,数组的函数,具有较多代码的函数。其实具体的看编译器的支持程度。有的支持简单递归等情况。
(6)字符串变量、复数变量
string 类对象 (常用)、复数类模板(少用,即学即用)