目录
1.1 基本格式
#include <iostream>
using namespace std;
int main()
{
return 0;
}
1.2 输入输出
在C++中,可以市容cin和cout进行输入输出。
用法如下:
cin >> “待输入项1” >> “待输入项2” >> ...;
cout << “待输出项1” << “待输出项2” <<...;
其中“待输入项” / “待输出项”可以是各种基本类型的变量、常量和表达式。
1.3 头文件
在C++中,头文件不再以“ .h”结尾。一些C语言中常用的头文件在C++中的名字变为去掉“.h”,并在开头增加字符c。例如:
#include <cstdio>
#include <cstring>
#include <cstdlib>
1.4 强制类型转换运算符的新形式
在C++中,强制类型转换可以写成以下形式:
类型名(待转换的表达式)
例如,int(3.5)、double(2)、double(a)等看起来像函数调用,比C语言中(int)3.5、(double)a的写法更清晰。
1.5 函数参数的默认值
在C++中,声明一个函数时,可以为函数的参数指定默认值。当调用有默认参数值的函数时,可以不写出参数,这时就相当于以默认值作为参数调用该函数。
例如:
void Function1(int x = 20); //函数的声明中,指明参数x的默认值为20
...
Function1(); //正确的调用语句,等效于Function1(20)
不仅可以用常数,还可以用任何有定义的表达式作为参数的默认值。例如:
int Max(int m, int n);
int a, b;
void Function2(int x, int y = Max(a, b), int z = a*b) {
...
}
Function2(4); //正确,等效于Function2(4, Max(a, b), a*b);
Function2(4, 9); //正确,等效于Function2(4, 9, a*b);
Function2(4, 2, 3); //正确
Function2(4, , 3); //错误!这样的写法不允许,省略的参数一定是最右边连续的几个
函数参数的默认值可以写在声明函数的地方(如Function1),也可以写在定义函数的地方(如Function2),但不能两个地方都写。
函数默认参数所带来的好处是使程序的可扩充性更好,即当程序需要增加新功能时,改动可以尽可能的少。例如:
void Circle(int r, int Point(x, y));
//一个绘图程序,画圆都是黑色的,现希望增加画彩色圆的功能,故需要增加一个int型的color参数
...
void Circle(int r, int Point(x, y), int color = 0);
//当设color默认值为0(黑色)时,只需要在需要彩色时补上颜色参数,
//而非对所有画圆的Circle函数都补上颜色参数,显然要简洁的多
1.6 引用和函数参数的传递
1.6.1 引用的概念
在C++中可以定义“引用”。定义方式如下:
类型名 & 引用名 = 同类型的某变量名;
此种写法就定义了一个某种类型的引用,并将其初始化为引用某个同类型的变量。“引用名”的命名规则和普通变量相同。例如:
int n; //r就是一个引用,也可以说r的类型是int &
int & r = n; //该语句使得r引用了变量n,也可以说r成为了n的引用
注意:定义引用时一定要将其初始化,否则编译无法通过。通常有以下几种初始化方法:1.用某个变量来初始化。2.用一个引用去初始化另一个引用。3.不能用常量初始化引用,也不能用表达式初始化引用(除非该表达式的返回值是某个变量的引用)
类型为T & 的引用和类型为T的变量是完全兼容的,可以互相赋值。
1.6.2 引用作为函数的返回值
函数的返回值可以是引用。例如:
#include <iostream>
using namespace std;
int n = 4;
int & SetValue()
{
return n; //返回对n的引用
}
int main()
{
SetValue() = 40; //返回值是引用的函数调用表达式,可以作为左值使用
cout << n << endl; //输出40
int & r = SetValue();
cout << r << endl; //输出40
return 0;
}
引用作为函数的返回值,其用途会在后面的“运算符重载”(如下标 [ ] )和“标准模板库”章节中介绍。
1.6.3 参数传值
在C++中,函数参数的传递有两种方式:传值和传引用。在函数的形参不是引用的情况下,参数传递方式是传值的。传引用的方式要求函数的形参是引用。
“传值”是指,函数的形参是实参的一个拷贝,在函数执行的过程中,形参的改变不会影响实参。例如:
#include <iostream>
using namespace std;
void Swap(int a, int b)
{
int tmp;
//以下三行将a、b的值互换
tmp = a;
a = b;
b = tmp;
cout << "In Swap:a=" << a << " b=" << b << endl;
}
int main()
{
int a = 4, b = 5;
Swap(a, b);
cout << "After swaping:a=" << a << " b=" << b;
return 0;
}
上面程序的输出结果是:
In Swap:a=5 b=4;
After swaping:a=4 b=5
输出结果说明,在Swap函数内部,形参a、b的值确实发生了互换,但是在main函数中,a、b还是维持原来的值。也就是说,形参的改变不会影响实参。原因暂略,与储存方式有关。
1.6.4 参数传引用
如果函数的形参是引用,那么参数的传递方式就是传引用的。在传引用的方式下,形参是对应的实参的引用,也就是说形参的改变会影响实参。将上面的函数稍加改动:
#include <iostream>
using namespace std;
void Swap(int & a, int & b)
{
int tmp;
//以下三行将a、b的值互换
tmp = a;
a = b;
b = tmp;
cout << "In Swap:a=" << a << " b=" << b << endl;
}
int main()
{
int a = 4, b = 5;
Swap(a, b);
cout << "After swaping:a=" << a << " b=" << b;
return 0;
}
上面程序的输出结果是:
In Swap:a=5 b=4;
After swaping:a=5 b=4
1.6.5 常引用
定义引用时,可以在前面加const关键字,则该引用就成“常引用”。如:
int n;
const int & r = n;
上面语句定义了常引用r,其类型是const int &。
常引用和普通引用的区别在于:不能通过常引用去修改其引用的内容。注意,不是常引用所引用的内容不能被修改,只是不能通过常引用去修改而已。
int n = 100;
const int & r = n;
r = 200; //编译出错,不能通过常引用修改其引用的内容
n = 300; //没问题,n的值变为300
注意,const T & 和T & 是不同的类型。T & 类型的引用或T类型的变量可以用来初始化const T & 类型的引用,const T类型的常变量和const T &类型的引用则不能用来初始化T & 类型的引用,除非进行强制类型转换。例如:
void Func(char & r) { }
void Func2(const char & r) { }
int main()
{
const char cc = 'a';
char c;
const char & rc1 = cc;
const char & rc2 = c; //char变量可以用来初始化const char & 的引用
char & r = cc; //编译出错,const char类型的常变量不能用来初始化char &类型的引用
char & r2 = (char &)cc; //没问题,强制类型转换
Func(rc1); //编译出错,参数类型不匹配
Func2(rc1); //没问题,参数类型匹配
return 0;
}
1.7 内联函数
作为特别注重程序执行效率,适合编写底层系统软件的高级程序设计语言,C++用inline关键字较好地解决了函数调用开销的问题。
在C++中,可以在定义函数时,在返回值类型前面加上inline关键字。如:
inline int Max(int a, int b)
{
if(a > b)
return a;
return b;
}
增加了inline关键字的函数称为“内联函数”。内联函数和普通函数的区别在于:当编译器处理调用内联函数的语句时,不会将该语句编译成函数调用的指令,而是直接将整个函数体的代码插入调用语句处,就像整个函数体在调用处被重写了一遍一样。有了内联函数,就能像调用一个函数那样方便地重复使用一段代码,而不需要付出执行函数调用的额外开销。很显然,使用内联函数会使最终可执行程序的体积增加。以时间换取空间,或增加空间消耗来节省时间,这是计算机学科中常用的方法。
另外,需要注意的是:1.内联函数中的代码应该只是很简单、执行很快的几条语句。2.调用内联函数的语句前必须已经出现内联函数的定义(即整个函数体),而不能只出现内联函数的声明。
1.8 函数的重载
C++不允许变量重名,但是允许多个函数取相同的名字,只要参数表不同即可,这叫做函数的重载。在调用同名函数时,编译器是根据函数调用语句中实参的个数和类型来判断应该调用哪个函数的。例如:
#include <iostream>
using namespace std;
int Max(int a,int b)
{
cout << "Max 1" << endl;
}
double Max(double a,double b)
{
cout << "Max 2" << endl;
}
double Max(double a,double b,double c)
{
cout << "Max 3" << endl;
}
int main()
{
Max(3,4); //调用int Max(int,int)
Max(2.4,6.0); //调用double Max(double,double)
Max(1.2,3.4,5); //调用 double Max(double,double,double)
Max(1,2,3); //调用double Max(double,double)
Max(3,1.5); //编译出错:二义性
return 0;
}
如果去掉第21行编译出错的语句,输出结果是:
Max 1
Max 2
Max 2
Max 3
在两个函数同名二参数个数不同,但是其中参数多的那个函数的参数又可以取默认值的情况下也可能会引发二义性。例如:
int Sum(int a, int b, int c = 0)
int Sum(int a, int b)
需要强调一点,同名函数只有参数表不同才能算重载。两个同名函数的参数表相同而返回值类型不同不是重载,而是重复定义,是不允许的。
1.9 指针和动态内存分配
数组的长度是预先定义好的,在整个程序中固定不变。C++不允许定义元素个数不确定的数组。但是在实际编程中,往往会出现所需的内存空间大小取决于实际要处理的数据多少,而实际要处理的数据数量在编程时无法确定的情况。
为了解决上述问题,C++提供了一种“动态内存分配”机制,使得程序可以在运行期间,根据实际需要,要求操作系统临时分配一片内存空间用于存放数据。此种内存分配是在程序运行中进行的,而不是在编译时就确定的,因此称为“动态内存分配”。
在C++中,通过new运算符来实现动态内存分配。
第一种用法:T是任意类型名,P是类型为T*的指针。这样的语句会动态分配处一片大小为sizeof(T)字节的内存空间,并将该内存空间的起始地址赋给P。例如:
int* p;
p = new int;
*p = 5;
第二种用法(用来动态分配一个任意大小的数组):T是任意类型名,P是类型为T*的指针,N代表“元素个数”,可以是任何值为正整数的表达式,表达式中可以包含变量、函数调用等,这样的语句动态分配主N*sizeof(T)个字节的内存空间,并将该内存空间的起始地址赋给P。例如:
int* pn;
int i = 5;
pn = new int[i*20];
pn[0] = 20;
pn[100] = 30;
如果要求分配的空间太大,操作系统找不到足够的内存来满足,那么动态内存分配就会失败,此时程序会抛出异常。关于这点,在11.2节“C++异常处理”中会介绍。
程序从操作系统动态分配所得的内存空间在使用完后应该释放,交还操作系统,以便操作系统将这片内存空间分配给其他程序使用。C++提供delete运算符,用以释放动态分配的内存空间。delete运算符同样有两种用法,对应两种new运算符的用法:
int* p;
p = new int;
*p = 5;
delete p;
delete p; //本句会导致程序出错,因为上一语句释放后p指针没有指向任何动态分配的内存空间
int* pn;
int i = 5;
pn = new int[i*20];
delete [] p;
delete [] p; //同样会报错
牢记,用new运算符动态分配的内存空间,一定要用delete运算符释放。否则会造成“内存泄露”,导致操作系统运行速度变慢(但是只要重启计算机,这种情况就会消失)。另外还要注意,释放一个指针,并不会使该指针的值变为NULL。
1.10 用string对象处理字符串
用字符数组存放字符串容易发生数组越界的错误,而且往往难以察觉。因此,C++标准模板库设计了string数据类型,专门用于字符串处理。string类型的变量就是用来存放字符串的,也叫“string对象”。要使用string对象,必须包含头文件string。
在用C++编程时,要优先考虑用string对象来处理字符串,因为其用法比字符数组要更简单,而且不容易出错。详细介绍见10.13 string类详解。