C++是贝尔实验室在20世纪80年代在C语言的基础上开发成功的。最初C++对C语言的改进体现在增加了适用于面向对象程序设计的 “类(class)”,所以也被称为 “带类的C”。
在处理较小规模的程序时,使用C语言能够得心应手,但当问题比较复杂、像C语言这样的结构化程序设计方法就显现出它的不足:C语言的设计者必须细致的设计程序中的每一个细节,准确地考虑程序运行时每一时刻发生的事情。这比较适合一个人搞定一个程序的情形,如果是大规模的程序涉及到多人分工合作,软件设计危机就出现了。
面向对象程序设计是针对开发较大规模的程序而提出来的,目的是提高软件开发的效率。
一、C++的输入和输出
C++之中的 cout 用于代替C语言中的 printf( ),cin 用于代替C语言中的 scanf( )。
1.1 cout 使用要点:
cout 必须和输出运算符 “<<” 一起使用,“<<” 运算符的结合性为从左向右。
用 “cout << ” 可以输出任何类型的数据,但是每输出一项要用一个 “<<”,不能一个 “<<” 后面接多个输出项!
使用 cout 对某个输出项进行输出显示的时候,可以使用 setw 控制符来指定输出所占的列数,例如:
float a = 1.23; cout << "a =" << setw(6) << a << endl;
输出结果为:
a = ︺︺1.23 (其中︺代表输出一个空格)
由上可知,如果输出项的实际长度不足所指定的列宽,则数据输出时 “右对齐” 。
1.2 cin使用要点:
cin 是从键盘到内存流动的数据流,cin必须和提取运算符 “>>” 一起使用。其作用是从键盘这个输入设备中提取数据送到输入流 cin 中。
int a, b, c; cin >> a >> b; c = max(a, b);
注意:在键盘上输入变量 a 和 b 的数值的时候,两个数据间使用1个或者多个空格,不能用逗号或者其他符合间隔!这一点不能去类比 C 语言之中的 scanf( ) 用法。
=================================================
scanf 函数的一般形式:
scanf(格式控制字符串,地址列表)
其中 “格式控制” 的含义与 printf 函数相同。“地址列表” 是由若干个地址构成,可以是变量的地址,或者字符串的首地址。
1)例如:
#includevoid main() { int a, b, c; scanf("%d%d%d", &a, &b, &c); printf("a=%d, b=%d, c=%dn", a, b, c); }
上述格式控制字符串 “%d%d%d” 表示要按十进制整数形式连续输入3个数据。输入数据时,在两个数据之间以一个或者多个空格分割,也可以使用Enter键、Tab键,下面的输入均合法:
3︺4︺5 ↙(其中︺代表输入一个空格,↙代表输入Enter键)
3↙
4︺5↙
3┌ ┬ ┐4↙ (其中┌ ┬ ┐代表输入一个Tab键)
5↙
如果在 scanf 的格式控制字符串中除了格式声明以外、还有其他字符,则通过键盘输入数据时:在对应位置应输入与这些字符相同的字符。
2)例如:
2.1 使用逗号间隔
scanf("%d,%d,%d", &a, &b, &c);
那么键盘输入应采用如下相应的形式:
3,4,5↙ (在输入的两个数据之间也应插入一个逗号)
2.2 使用2个空格间隔:
scanf("%d %d %d", &a, &b, &c);
那么,通过键盘进行输入的时候,两个数据之间应有2个或者更多的空格字符:
3︺︺4︺︺5↙
3︺︺︺︺4︺︺︺5↙
2.3 使用冒号间隔:
scanf("%d:%d:%d", &a, &b, &c);
那么,通过键盘进行输入的时候,两个数据之间就应当采用冒号间隔:
3:4:5↙
2.4 使用更多必要信息进行间隔:
scanf("a=%d,b=%d,c=%d", &a, &b, &c);
那么,通过键盘进行输入的时候,这些必要信息也要从输入进去:
a=3,b=4,c=5↙
采用这种方式有一个好处,使含义更加清晰,不容易发生输入数据的错误。
3)用 “%c” 格式声明输入字符,例如:
scanf("%c%c%c", &a, &b, &c);
它表示连续输入3个字符,即3个按键,而不是连续输入3个数组(与上述2.1 ~ 2.4不同)。
xyz↙
中间不能使用空格或者其他间隔信息,否则空格或者其他信息也是当成这3个连续字符中的字符。
========================================================================
小结:
在用 “%d” 格式声明输入10进制数据时,遇到输入空格键、Enter键、Tab键或者 0 ~ 9 数值以外的其他非法输入,则认为该数据结束。
在用 “%c” 格式声明输入字符数据时,键盘上的所有按键都会当成一个字符,所以输入的字符与字符之间不能增加任何间隔符。
二、变量的引用
在C++中,变量的 “引用” 就是变量的别名,因此引用又称为别名(alias)。对一个变量的 “引用” 的所有操作,实际上都是对其所表示的变量的操作。
对于一个变量a,想给它起一个别名b,可以这样写:
int a; int &b = a;
这就声明了 b 是 a 的引用,即 a 的别名。
经过上述声明以后,使用 a 或者 b 的作用相同,它们都表示同一变量。
注意:
1) 对变量声明一个引用,并不另开辟内存单元,b 和 a 都表示同一内存位置。因此在建立引用关系时,只有声明、没有定义,只是声明它和原有的某一变量的关系。
2) 在同一个函数内,声明了一个引用,那么在本函数执行期间,该引用就一直与其表示的变量相关联,不能再将其声明为其他变量的别名。以下用法不行:
int a1, a2; int &b = a1; int &b = a2; // 用法错误
引用并不是一种独立的数据类型,它必须与某种类型的数据相关联。在声明一个引用时(用作函数形参除外),必须同时使之初始化,即声明它表示哪一个变量(是哪一个变量的别名)。
3)如何区分 “声明引用” 还是 “取地址” 操作?
当 &a 的前面带数据类型时,它必然是对引用的声明;
当 &a 的前面不带数据类型时(如 p = &a),此时的 & 是取地址运算符。
4)将引用作为函数参数:
C++中增加 “引用” ,主要是利用它作为函数参数,以扩充函数传递数据的功能。例如:
void swap(int &a, int &b)
{
//
}
在上述 swap 函数的形参列表中,声明 a 和 b 是整型变量的引用,但此时并未对其进行初始化,即未指定它们是哪两个变量的别名。对引用型形参的初始化是在函数调用时、实参把变量名传给形参来实现的。实参传给形参的是实参的地址,也就是使形参和实参具有同样的地址,从而使实参和形参共享同一内存位置。
将引用作为函数参数,称为引用方式调用(call by reference)。但是 C 语言只支持另一种形式的函数调用:传值方式调用(call by value)—— 将实参的值传送给形参,形参是实参的一个拷贝。
5)对引用的进一步说明:
5.1 不能建立 void 类型的引用,如:
void &a = 9; // 错误
任何实际存在的变量都是非 void 类型的。
5.2 不能建立引用的数组,如:
char c[6] = "hello"; char &x[6] = c; // 错误
试图建立一个包含 6 个元素的引用的数组,这样不行,引用是变量的别名,数组名是数组首元素的地址、并不是一个变量。
5.3 可以将一个变量的引用的地址赋给一个指针:
int a = 3; int &b = a; int *p = &b; // 可行,相当于p指向变量a
但是不能定义指向引用类型的指针变量,不能写成:
int& *p = &a; // 错误
因为引用不是一种独立的数据类型,所以不能建立指向引用类型的指针变量!
5.4 可以建立指针变量的引用:
因为指针变量也是一个变量,指针变量的引用的写法如下:
int i = 5; int *p = &i; int* &p_1 = p; // 正确,指针变量的引用(int* 是一个整体,代表int型指针这种数据类型)
5.5 可以用 const 对引用加以限定,不允许改变该引用的值:
例如:
int i = 5; const int &a = i; a = 3; // 错误 i = 3; // 可行,上述const只限定引用,并不阻止改变引用所表示的变量的值
这一特征在使用引用作为函数形参时非常有用!因为有时候会希望保护形参的值不被改变。
5.6 可以用常量或者表达式对引用进行初始化,但必须用const声明:
例如:
int i = 5; const int &a = i + 3; // 可行,用const声明
此时,a 是一个临时变量的别名,这个临时变量是由编译系统内部处理的:
编译系统将 “const &a = i + 3;” 转换成:
int temp = i + 3;
const int &a = temp; // 声明a是临时变量temp的别名
用户不能访问该临时变量!
借助编译系统的这种处理方式,不仅可以像上面一样用表达式对引用进行初始化,还可以用不同类型的变量对引用进行初始化,但是前提也是必须用const声明:
例如:
double d = 3.1415926; const int &a = d;
编译系统在处理这种情况的时候,也在内部定义了一个临时变量:
编译系统将上述 “const in &a = d;” 转换成:
int temp = d;
const int &a = temp; // 声明a是临时变量temp的别名
如果不用const声明,则编译系统会报错!即编译系统内部也不会按照如上定义临时变量的方式去处理。
三、字符串变量
在 C 语言中,只有字符常量、字符变量、字符串常量和字符数组,没有字符串变量。
C++ 之中,增加了字符串变量 —— 用字符串类型(string类型)定义字符串变量。
例如:
string string1; string string2 = "Huawei";
1) 对字符串变量的赋值:
既可以用字符串常量给字符串变量赋值(只能在定义字符串变量的时候这样赋值),也可以用字符串变量给字符串变量赋值。
例如:
string string1 = "China"; string string2 = "Universe"; string1 = string2; // 可行,在定义字符串变量时不需指定长度,长度随其中的字符串长度而改变
在执行上述赋值语句之前,字符串变量 string1 的长度为5,执行赋值语句以后长度变成8。
2) 字符串变量可以按照数组的方式进行操作:
例如:
string a = "Huawei"; a[0] = 'h';
3)用字符串常量给字符串变量赋值:
例如:
string string1; string1 = "Hello!";
字符串常量以 ' ' 作为结束符,但将字符串常量存放到字符串变量之中时,只存放字符串本身、而不包括结束符 ' '。
而字符串常量是不能给字符数组进行赋值的:
char str[10]; str = "Hello!"; // 错误
只能使用strcpy函数将字符串复制到字符数组中,并且在复制的时候会将字符串常量后面的结束字符 ' ' 一并复制到字符数组中,例如:
char str[10]; strcpy(str, "Hello!");
或者在定义字符数组的时候用字符串常量给字符数组赋初值。例如:
char a[] = "CHINA";
或
char a[] = {"CHINA"};
字符数组 a 的长度为 6(5+1)。因为字符串常量借助字符数组在内存中进行存储,编译系统会在字符数组中自动添加一个结束符 ' '。
对于固定长度的字符数组,使用 scanf 从键盘输入字符串的时候对长度有讲究,例如:
char A_string[20];
scanf("%s", &A_string);
键盘输入字符串的实际长度必须 ≤ 19(字节),因为通过键盘输入完成以后,系统会自动在字符串的最后添加一个结束字符 ' ' 。
综上所述,C++之中引入字符串变量(string)使得字符串的处理更加方便!在向字符串变量赋值时不必精确计算字符个数,不必顾虑是否会 “超长” 而影响系统安全。
4) 字符串变量的输入输出:
cin >> string1; // 从键盘输入一个字符串给字符串变量 cout << string2; // 将字符串string2输出显示
5) 字符串变量的运算:
在用字符数组存放字符串时,字符串的运算要用字符串函数。而对于string类对象,可以不用这些函数、直接用简单的运算符。
5.1 用赋值运算符实现字符串复制
例如:
string1 = string2;
其作用与 strcpy 函数相同!
5.2 用加法运算符实现字符串连接
例如:
string1 = "C++ "; string2 = "Language"; string1 += string2; // 连接string1和string2
其作用与 strcat 函数相同!
5.3 用关系运算符实现字符串比较
可以直接用 ==,>,<,!=,>=,<= 等关系运算符进行字符串的比较。
其作用与 strcmp 函数相同!
6) 字符串数组
不仅可以用 string 定义字符串变量,也可以用 string 定义字符串数组。例如:
string name[5];
string name[5] = {"Zhao", "Qian", "Sun", "Li", "Zhou"};
对于字符串数组,并不要求每个字符串元素具有相同的长度。即使对同一个元素而言,它的长度也是可以变化的,例如向某一个元素重新赋值时、其长度就可能发生变化。
每一个字符串元素中只包含字符串本身的字符,而不包含 ' ' 。
字符串数组之所以允许字符串元素的长度可变,是因为字符串数组的元素中存放的是字符串的地址、而不是字符串本身。可以使用 sizeof( string ) 和 sizeof( name[0] ) 来观察结果数值。
四、动态内存分配和释放
C语言中,利用 malloc 和 free 库函数实现内存的动态分配和释放。
但是,malloc 函数调用的时候必须指定大小、且返回值一律为 void* 类型,必须在程序中进行强制类型转换,才能使其返回的指针指向具体的数据。
C++ 提供运算符 new 和 delete 来取代 malloc 和 free 函数。例如:
new int; // 开辟一个存放整型数据的内存空间 new int(100); // 开辟一个存放整型数据的内存空间,并赋初值100 new char[10]; // 开辟一个具有10个元素的字符数组的内存空间 new int[3][2]; // 开辟一个存放二维整型数组的内存空间 float *p = new float(3.14); delete p; char *p = new char[10]; delete [] p; // 表示对数组空间的释放操作
注意:C++ 中的 new 和 delete 是运算符,而不是函数,所以它们的执行效率更高!
五、类(class)和结构体类型(struct)
C 语言中的结构体类型,只有成员变量。
C++ 在增加类(class)的同时,也对结构体类型的功能进行了扩展:C++ 允许用 struct 来定义一个类(class)类型,例如:
struct {
public:
void display()
{
cout << "num:" << num << endl;
cout << "name:" << name << endl;
cout << "sex:" << sex << endl;
}
private:
int num;
char name[20];
char sex;
};
那么,此时使用 struct 关键字声明的结构体类型实际上也就是类。所以,C++ 之中的结构体类型既有成员变量,又可以有成员函数。
用 struct 声明的类和用 class 声明的类是有区别的:
★ 用 struct 声明的类,如果对其成员不作 public 或 private 的声明,系统将其默认为 public;
★ 用 class 声明的类,如果不作 public 或 private 的声明,系统将其成员默认为 private。
C 语言中,定义结构体变量的时候,可以给成员变量赋初值,例如:
struct student
{
int num;
char name[20];
char sex;
};
struct student student1 = {10101, "Li lin", 'M'};
struct student student2 = {10102, "Liu li", 'F'};
C++ 中,在定义类的对象时,如果一个类中所有的成员变量都是 public 的,则可以在定义的时候对成员变量进行初始化,例如:
class Time
{
public:
int hour;
int minute;
int second;
};
Time t = {10,11,12}; // 将时间t初始化为 10:11:12
这种情况,和结构体成员变量赋初值是差不多的。
但是,如果类的成员变量是 private 的、或者类中有 private 或 protected 的成员,则不能采用这种类似于结构体赋初值的方式,可以用构造函数来对对象中的成员变量赋初值。
1) 类的成员函数
类(class)是一种数据类型(广义的数据类型),这种数据类型中的数据既包含成员变量、又包含成员函数。
类型是抽象的,它不占用内存,而类的对象是具体的,占用内存空间。因为不同对象的数据存储单元中存放的成员变量的值是不同的,但是不同对象的成员函数的代码是相同的,所以C++编译系统为每个对象分配的存储空间只包含成员变量所占用的存储空间、而不包括成员函数代码所占用的存储空间。所以,如果要计算类的对象占用的存储空间大小,只需要看其成员变量部分即可、完全不用考虑成员函数代码所占用的存储空间 —— 这样就跟结构体类型的变量差不多。
2) 构造函数
构造函数是一种特殊的成员函数,函数名与类的名称相同。
构造函数是在定义对象的时候系统自动调用的,并且只执行一次,而且构造函数一般声明为 pubilic。
构造函数的主要作用是对对象进行初始化,因为不带参数的构造函数对所有对象的成员变量执行同样的初始化,不能做到不同的对象在定义的时候执行不同的初始化 —— 所以就出现了带参数的构造函数!例如:
class Box
{
public:
Box(int h, int w, int len)
{
height = h;
width = w;
length = len;
}
private:
int height;
int width;
int length;
};
Box box1(10, 20, 30);
Box box2(40, 50, 60);
由于构造函数是在对象定义的时候被系统调用的,所以可以按照上述方式:在定义对象的时候给对象的带参数的构造函数传递实参。
box1 和 box2 是定义的类的对象;
紧跟在后面括号内的数据是传递给带参数的构造函数的实参。
值得说明的是,以上通过带参数的构造函数来实现对象成员变量的初始化:是在类体中定义了一个带参数的构造函数、并通过逐一赋值的方式完成成员变量的初始化。
其实还有一种更加简练和方便的带参数的构造函数的写法 —— 采用参数初始化列表:
class Box
{
public:
Box(int h, int w, int len): height(h),
width(w),
length(len)
{
}
private:
int height;
int width;
int length;
};
许多C++ 程序员喜欢采用这种方式初始化所有成员变量。



