1 C++概述
C++与C语言的关系:C语言是C++的一个子集,C++包含了C语言的全部内
容。1、C++保持与C语言的兼容,现有的许多C代码不经修改就可以为C++所用。
2、C++对C语言作了很多改进:
①增加了一些新的运算符,使得C++应用起来更加方便。
②改进了类型系统,增加了安全性。
③引进了“引用”概念,使用引用作函数参数带来了很大方便。
④允许函数重载,允许设置缺省参数,这些措施提高了编程的灵活性,减少
冗余性。又引进了内联函数的概念,提高了程序的效率。
⑤对变量说明更加灵活了。可以根据需要随时对变量进行说明。
2 C++程序结构
#include<iostream>
using namespace std;
int main()
{
double x,y;
cout<<"Enter two float numbers:";
cin>>x>>y;
double z = x + y;
cout<<"x+y="<<z<<endl;
}
说明:
• 1)C++允许的新的注释形式以//开始,直到本行的末尾的
文字都是注释。
• 2)iostream是一个头文件,定义了标准的输入和输出操
作,包括对cin和cout的说明。
• 3)cout称为标准输出流,表示标准输出设备,一般指屏幕。
cin表示标准输入设备,一般指键盘。
• 4)<<和>>是重载的运算符,<<将其右边的内容输出到屏
幕上。>>将键盘中输入的一个数,送到它右边的变量中保
存起来。
5)endl表示输出新行。
• C++程序的源文件约定使用扩展名.cpp或.cxx,头文件约定
使用扩展名.h 或.hpp或.hxx。编辑好的源程序经过C++编译
器编译成目标文件,其扩展名是.obj,再经过C++连接器,
将目标文件与库文件中的一些代码连接起来,生成一个可
执行文件。程序被运行后,一般在屏幕上显示出运行结果。
3 C++的I/O流cin和cout
在C++中提供了新的输入/输出方式。其主要目标是建立一个类型安全、扩充性好的输入/输出系统。C++的输入/输出流库是建立在流的概念上。流类似于文件,可以把流看成是一个无限长的字符序列,它可以被顺序访问。从流中获取数据的操作称为提取操作。向流中添加数据的操作称为插入操作。C++的输入/输出流库不是语言的一部分,而是作为一个独立的函数库提供的。因此,在使用时需要包含相应的头文件“iostream.h”。输出操作被认为是插入过程,由重载的插入符“<<”来实现。输入操作被认为是提取过程,由重载的提取符“>>”来实现。
4 函数的重载
函数重载(overloading)是指一个函数可以和同一作用域中的其他函数具有相同的名字,即同一个函数名可以对应着多个不同的函数实现。C++中允许两个或多个函数共用同一个函数名,但这些函数各自拥有可用于区分和唯一识别它们的参数表。它们之间有的是通过参数表中某个参数的类型不同来区别,有的是通过参数个数的不同加以区别。
1、参数类型上不同的重载函数
#include <iostream>
using namespace std;
• int add(int ,int);
• double add(double,double);
• void main( )
• { cout<<add(3,6)<<endl;
• cout<<add(4.6,9.0)<<endl; }
• int add(int a, int b)
• { return a+b; }
• double add(double a, double b)
• { return a+b; }
2、参数个数上不同的重载函数
#include <iostream>
using namespace std;
• int max(int a, int b);
• int max(int a, int b, int c);
• void main( )
• { cout<<max(12,6)<<endl;
• cout<<max(5,9,-12)<<endl; }
• int max(int a, int b)
• { return a>b?a:b; }
• int max(int a,int b, int c)
• { int t;
• if (a>=b) t=a;
else t=b;
• if (c>t) t=c;
• return t; }
函数重载要求编译器能够唯一地确定调用一个函数时应执行哪个函数代码,即采用哪个函数实现。确定函数实现时,要求从函数参数的个数和类型上来区分。也就是说,进行函数重载时,要求同名函数在参数个数上不同,或者参数类型上不同。否则,将无法实现重载。
使用函数重载主要是为了处理一组完成相同或相似功能的任务,但处理的数据个数或类型不同,这样,编程时可以不必费力的给它们起名和记忆。
如果两个函数参数个数和类型完全相同,仅仅是返回值不同,它们不是重载的函数。程序中出现这样两个函数,编译时将出错。
函数重载可以使某些具有相似功能的函数聚集起来共同使用一个通常具有特定语义的函数名,但是当聚集起来的函数并不执行相似的操作时,就不应采用函数重载。
5 引用
引用也是一种特殊类型的变量,它通常被认为是另一个
变量的别名。定义引用变量的格式:
• 类型 &引用名=变量名;
•引用一般要立即进行初始化。无初始化的引用是无效的。
•引用与被引用的实体具有相同的地址,引用本身不能改
变,所有在引用上所施加的操作,实质上就是在被引用
者上的操作。
•例如: int i=5,&m =i;
•可以将一个引用赋给某个变量,则该变量将具有被引用
的变量的值。例如: int n=m;
这时,n具有被m引用的变量i的值,即10。
#include<iostream>
using namespace std;
int main()
{
int i = 5;
int &ri = i;
cout << "add_i=" << &i << "add_ri=" << &ri << endl;
cout << "i="<<i<<"ri="<<ri<<endl;
i *= 3;
cout<<"i="<<i<<"ri="<<ri<<endl;
ri += 5;
cout<<"i="<<i<<"ri="<<ri<<endl;
return 0;
}
#include<iostream>
using namespace std;
int main(int main,char *argv[])
{
int i = 5;
int *pi = &i;
int *&rpi = pi;
cout<<"add_pi="<<pi<<"add_rpi="<<rpi<<endl;
cout<<"i="<<i<<endl;
*pi = 10;
cout<<"i="<<i<<endl;
*rpi = 20;
cout<<"i="<<i<<endl;
return 0;
}
C++使用引用的主要地方是建立函数变参。将引用作为函数的形参时,改变形参的值会影响实参的值。C++使用引用参数另一个目的是为了效率。如果函数的参数是类或结构类型,参数传递时将拷贝整个形参。使用引用可以只传递指向形参的指针。引用调用是C++中的一种函数调用方式,C语言中没有这种调用方式。
#include<iostream>
using namespace std;
void swap(int &m,int &n)
{
int temp = m;
m = n;
n = temp;
}
int main()
{
int a = 2,b = 4;
swap(a,b);
cout<<"a="<<a<<endl;
cout<<"b="<<b<<endl;
return 0;
}
运算结果:
• a=4
• b=2
•使用引用作函数形参时,调用函数的实参要用变量名,将实参变量名赋给形参的引用,相当于在被调用函数中使用了实参的别名。于是在被调用函数中,对引用的改变,实质上就是直接地通过引用来改变实参的变量值。而且这种调用起到传址调用的作用,但它又比传址调用更方便、更直接。因此,在C++中常常使用引用作函数形参来实现在被调用函数中改变调用函数的实参值。
•使用引用作形参但不改变实参值,可以如下定义函数原型:
• int myFunc(const int & param);
•不能对param赋值,否则编译出错。
引用可以作为函数的返回值。返回引用的函数可以作为
左值,即它可以出现在赋值运算符的左边。
#include<iostream>
using namespace std;
int a[] = {1,3,5,7,9};
int &elem(int i)
{
return a[i];
}
int main()
{
int j;
for(j=1;j<5;j++)
elem(0) +=elem(j);
cout<<"elem(0)="<<a[0]<<endl;
return 0;
}
在实现返回引用的函数时,注意不要返回对该函数内的自动变量的引用,因为以后使用引用时,它所指向的变量已不存在。
引用和指针的区别
•1 引用被创建的同时必须被初始化;指针则可以在任
何时候被初始化.
• 2 不能有NULL引用,引用必须与合法的存储单元关
联;指针则可以是NULL.
• 3 一旦引用被初始化,就不能改变引用的关系;指针
则可以随时改变所指的对象.
6 内联函数
引入内联函数的目的是为了解决程序中函数调用的效率问题。程序执行过程中,每调用一次函数,就要在调用与返回过程中付出一定的时间与空间代价用于处理现场。当函数较小又反复使用时,处理现场的开销比重会急剧增大。若把函数体嵌入函数调用处,便可以大大提高运行速度,节省开销。内联函数就可以自动实现这一功能。
使用C++中新的关键字inline说明的函数称为内联函数。编译器在遇到对内联函数调用时,将尽可能的用内联函数的函数体替换函数调用的表达式。因此会增加目标程序代码量,它是以目标代码的增加为代价来换取时间的节省。使用内联函数可以加快程序执行的速度。
#include<iostream>
using namespace std;
inline int power_int(int x)
{
return (x)*(x);
}
int main()
{
int i;
for(i=1;i<=10;i++)
{
int p = power_int(i);
cout<<i<<"*"<<i<<"="<<p<<endl;
}
}
•在使用内联函数时,应注意如下几点:
• 1 在内联函数内不允许使用循环语句和开关语句。
• 2 内联函数的定义必须出现在内联函数第一次被调用之
前。
• 3 后面讲到的类结构中所有在类说明内部定义的函数都
是内联函数。
7 函数参数的缺省值
正确的缺省参数说明:
• void fun1(int x,int y=0,int z=0);
• void fun2(int x,int y=0);
• C++允许定义或说明函数时为一个或多个形参指定缺省值。缺省参数的说明必须在形参表的最右边开始,并且中间没有间隔的非缺省参数说明。缺省参数只能定义一次,如果在函数原型中已经指定了缺省参数,那么在函数定义时不能再次说明。
•如果在函数调用时指定了形参对应的实参,则形参使用实参的值;如果未指定相应的实参,则形参使用缺省值。例
如有如下的函数调用表达式:
• fun1(10);
• 它与下列调用表达式是等价的:
• fun1(10,0,0);
#include<iostream>
using namespace std;
void fun(int a = 1,int b = 3,int c = 5)
{
cout<<"a="<<a<<","<<"b="<<b<<","<<"c="<<c<<endl;
}
int main(int argc,char *argv[])
{
fun();
fun(7);
fun(7,9);
fun(7,9,11);
cout<<"OK!";
return 0;
}
该程序中在函数的定义时设置了参数的缺省值,而在调用该函数时,有的无实参,有的实参数目不足,有的实参数目与形参相等,分若干不同情况来说明缺省值的使用。
#include<iostream>
using namespace std;
int m = 8;
int add_int(int x,int y = 7,int z = m);
int main(int argc,char *argv[])
{
int a = 5,b = 15,c = 20;
int s = add_int(a,b);
cout<<s<<endl;
return 0;
}
int add_int(int x,int y,int z)
{
return x+y+z;
}
该程序中,在说明函数add_int()时,给函数参数设置了缺省值,而其中一个参数的值被设置为一个已知变量(m)的值。
8 作用域运算符
::是C++定义的一个新的运算符,称为作用域运算符。使用作用域运算符可以访问当前作用域外部的标识符。当::作为单目运算符时,它的右操作数是一个标识符,它限定访问全局作用域范围内的该标识符。当::是双目运算符时,它的左操作数是类名,右操作数是类的成员。它限定访问指定类的某个成员。::运算符最有用的地方是在派生类中访问基类的成员。尤其是当派生类定义的成员名字与基类中成员名字相同时(即派生类的成员名字覆盖基类的成员名字时)。
• int a, b; //全局作用域内定义的变量a,b
• int myClass::myFunc(int a)
• { myClass::a=b; //当名字存在二义性时,使用::限定访问的类的成员
• ::a=b; } //访问的是全局作用域内定义的变量a
•注意:在成员函数内访问全局变量是不好的程序设计风格,应尽量避免。
9 const修饰符定义常量
使用类型修饰符const说明的类型称为常类型,常类型变量的值是不能被更新的。因此,定义或说明常类型变量时必须进行初始化。任何一种改变常类型变量的值的操作都将导致编译错误。例如:
• int const m=15;
• m=18;
•这种赋值操作是错误的。因为前面定义了m是一个常量,并且给它初始化了,即m值为15,因此,不能再改变m的值了。
•一个没有初始化的常类型变量定义也会导致编译错误。例如:
• const double PI; //error ; uninitialized const
•试图把一个常类型变量的地址赋给一个指针同样会使编译器生成编译错误。否则,常类型变量的值将通过指针间接地修改。例如:
• const int a=4;
• int *p=&a; // error C2440: 'initializing' : cannot convert from
'const int *' to 'int *'
• *p+=5;
1、一般常量
•一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。
例如:
• int const x=2; 与 const int x=2; 是一样的。
• int const a[5]={1,2,3,4,5};
•说明数组a的各个元素是int型常量,即数组元素的值是不能
被更新的。
• 2、常指针
•使用const修饰指针时,由于const的位置不同,而含意不同。
•下面定义的是一个指向字符串的常量指针:
• char *const ptr1=stringptr1;
•其中,ptr1是一个常量指针。因此,下面赋值是非法的:
• ptr1=stringptr2;
•而下面赋值是合法的:
• *ptr1='m';
•因为指针ptr1所指向的变量是可以更新的。
下面定义了一个指向字符串常量的指针:
• const char *ptr2=stringptr1;
•其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串是不能更新的,而ptr2是可以更新的。因此,
• *ptr2='x';
•是非法的。而
• ptr2=stringstr2;
•是合法的。
•所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。
#include<iostream>
using namespace std;
const int N = 6;
void print(const int *p,int n);
int main(int argc,char *argv[])
{
int array[N];
for(int i = 0;i < N;i++)
cin>>array[i];
for(int i = 0;i < N;i++)
cout<<array[i];
print(array,N);
return 0;
}
void print(const int *p,int n)
{
cout<<"{"<<*p;
for(int i = 1;i < n;i++)
cout<<","<<*(p+i);
cout<<"}"<<endl;
}
•说明:该程序中两处出现const修饰符,一是使用const定义一个int型常量N;二是使用const定义一个指向常量数组的指针。该指针所指向的数组元素是不能被更新的。
•该程序中有一个问题:print()函数中,实参array是一个int型数组名,形参是const int的指针,显然类型不相同,但却没有出现类型错误。这是因为形参虽然是指向一个非const int型数组array,该数组是可以更新的,但在print()函数中不能被更新。因此,一个能够更新的变量使用在一个不能被更新的环境中是不破坏类型保护,所以不出现类型不匹配错。
• 3、使用const也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。
• 4、使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字说明的成员函数不能用来操作常对象。
10 new和delete
•在C++中,定义了两个新的运算符:new和delete,专门进行动态内存申请和释放,而且new能自动调用构造函数创建相应的类对象,delete能自动调用析构函数删除类对象。new和delete应匹配使用,如果delete运算符作用到不是用new返回的指针,可能引起程序运行错误。
•动态申请保存一个type类型的数据的内存:
• p=new type;
• p是指向类型type的指针,type是数据类型名。
•释放以前用new申请的保存一个type类型数据的内存:
• delete p;
•等号左边的类型必须与右边申请的类型一致,否则,编译
出错。
• new 返回分配的内存地址,应该将它保存在一个变量中,
以后用delete释放。
#include<iostream>
using namespace std;
int main(int argc,char *argv[])
{
int *p;
p = new int;
*p = 888;
cout<<"add_p="<<&p<<endl;
cout<<"add_m="<<p<<endl;
cout<<"value_*p="<<*p<<endl;
delete p;
return 0;
}
• new能自动计算它要分配的存储空间的大小,可以为更复杂的数据实体(如数组)分配空间,如语句:
• int *pi=new int[8];
•动态分配存放8个整数的内存空间。释放pi指向的数组存储区时,应使用下面的格式:
• delete [] pi;
• new可以在为简单变量动态分配内存空间的同时,进行初始化。例如:
• int *p=new int(10);
•在动态分配内存的同时,将这个动态存储区中的值初始化为10。但是,不能用new为动态分配的数组存储区进行初始化。