C++ 编程指南
■ C++环境安装
■ C++ 基本语法
■ 预定义宏
#include <iostream>
using namespace std;
int main ()
{
cout << "Value of __LINE__ : " << __LINE__ << endl;
cout << "Value of __FILE__ : " << __FILE__ << endl;
cout << "Value of __DATE__ : " << __DATE__ << endl;
cout << "Value of __TIME__ : " << __TIME__ << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Value of __LINE__ : 6
Value of __FILE__ : test.cpp
Value of __DATE__ : Feb 28 2011
Value of __TIME__ : 18:52:48
■ # 和 ## 运算符
■ C++ 引用
int i = 17;
int& r = i; //为 i 声明引用变量
double& s = d;
r = 14; //引用使用
■ C++ 命名空间
■ 定义命名空间
namespace namespace_name {
// 代码声明
}
使用命名空间变量或函数
name::code; // code 可以是变量或函数
示例一:
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
它会产生下列结果:
Inside first_space
Inside second_space
■ using 指令
使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。
这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
using namespace first_space; //这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
int main ()
{
// 调用第一个命名空间中的函数
func();
return 0;
}
using 指令也可以用来指定命名空间中的特定项目
using std::cout; //使用 std 命名空间中的 cout 部分
示例一:使用 std 命名空间中的 cout 部分
#include <iostream>
using std::cout;
int main ()
{
cout << "std::endl is used with std!" << std::endl;
return 0;
}
■ 嵌套的命名空间
命名空间可以嵌套
namespace namespace_name1 {
// 代码声明
namespace namespace_name2 {
// 代码声明
}
}
通过使用 :: 运算符来访问嵌套的命名空间中的成员:
// 访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
// 访问 namespace_name1 中的成员
using namespace namespace_name1;
示例一:嵌套的命名空间使用
#include <iostream>
using namespace std;
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
}
using namespace first_space::second_space;
int main ()
{
// 调用第二个命名空间中的函数
func();
return 0;
}
示例二:全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
#include <iostream>
using namespace std;
namespace A
{
int a = 100;
namespace B //嵌套一个命名空间B
{
int a =20;
}
}
int a = 200;//定义一个全局变量
int main(int argc, char *argv[])
{
cout <<"A::a ="<< A::a << endl;
cout <<"A::B::a ="<<A::B::a << endl;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
int a = 30;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
return 0;
}
结果:
A::a =100
A::B::a =20
a =200 //全局变量a
::a =200
a =30 //局部变量a
::a =200
■ String类
C++ 标准库提供了 string 类类型。
示例一:
#include <iostream>
#include <string>
using namespace std;
int main ()
{
string str1 = "runoob";
string str2 = "google";
string str3;
int len ;
// 复制 str1 到 str3
str3 = str1;
cout << "str3 : " << str3 << endl;
// 连接 str1 和 str2
str3 = str1 + str2;
cout << "str1 + str2 : " << str3 << endl;
// 连接后,str3 的总长度
len = str3.size();
cout << "str3.size() : " << len << endl;
return 0;
}
结果:
str3 : runoob
str1 + str2 : runoobgoogle
str3.size() : 12
–
■ 类
■ 类的static静态成员
- 类的变量和函数都可以被声明为静态的。
- static 关键字来把类成员定义为静态的。
- 当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
- 静态成员在类的所有对象中是共享的。
- 在创建第一个对象时,所有的静态数据都会被初始化为零
- 不能把静态成员的初始化放置在类的定义中。
- 在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
示例一:
#include <iostream>
using namespace std;
class Box
{
public:
static int objectCount;
// 构造函数定义
Box(double l=2.0, double b=2.0, double h=2.0)
{
cout <<"Constructor called." << endl;
length = l;
breadth = b;
height = h;
// 每次创建对象时增加 1
objectCount++;
}
double Volume()
{
return length * breadth * height;
}
static int getCount()
{
return objectCount;
}
private:
double length; // 长度
double breadth; // 宽度
double height; // 高度
};
// 初始化类 Box 的静态成员
int Box::objectCount = 0;
int main(void)
{
// 在创建对象之前输出对象的总数
cout << "Inital Stage Count: " << Box::getCount() << endl;
Box Box1(3.3, 1.2, 1.5); // 声明 box1
Box Box2(8.5, 6.0, 2.0); // 声明 box2
// 在创建对象之后输出对象的总数
cout << "Final Stage Count: " << Box::getCount() << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Inital Stage Count: 0
Constructor called.
Constructor called.
Final Stage Count: 2
■ C++ 继承
已有的类称为基类,新建的类称为派生类。
示例一:基类 & 派生类
// 基类
class Animal {
// eat() 函数
// sleep() 函数
};
//派生类
class Dog : public Animal {
// bark() 函数
};
■ 继承类型 public、protected 或 private
■ 访问控制和继承
派生类可以访问基类中所有成员访问权限如下:
一个派生类继承了所有的基类方法,但下列情况除外:
基类的构造函数、析构函数和拷贝构造函数。
基类的重载运算符。
基类的友元函数。
■ 多继承
多继承即一个子类可以有多个父类,它继承了多个父类的特性。
class <派生类名>:<继承方式1><基类名1>,<继承方式2><基类名2>,…
{
<派生类类体>
};
// 派生类
class Rectangle: public Shape, public PaintCost
{
public:
int getArea()
{
return (width * height);
}
};
■ 数据抽象
类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
数据抽象是一个把实现细节与相关的数据分离开的概念。
#include <iostream>
using namespace std;
class Adder{
public:
// 构造函数
Adder(int i = 0)
{
total = i;
}
// 对外的接口
void addNum(int number)
{
total += number;
}
// 对外的接口
int getTotal()
{
return total;
};
private:
// 对外隐藏的数据
int total;
};
int main( )
{
Adder a;
a.addNum(10);
a.addNum(20);
a.addNum(30);
cout << "Total " << a.getTotal() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Total 60
■ 虚继承
class A {
public:
A() {
cout << "A(): " << endl;
}
};
class B : virtual public A { //
public:
B() :A() {
cout << "B():A(): " << endl;
}
};
class C : virtual public A {
public:
C() :A() {
cout << "C():A(): " << endl;
}
};
class D : public C, public B {
public:
D() {
cout << "D() " << endl;
}
};
void test() {
D d;
}
这个输出代表了对象的构造顺序。根据输出可以看出:
- 首先,类 A 的构造函数被调用,输出 "A(): "。
- 接着,类 C 的构造函数被调用,由于 C 类继承了虚基类 A,所以会先调用 A 的构造函数,输出 "C():A(): "。
- 然后,类 B 的构造函数被调用,同样会先调用 A 的构造函数,输出 "B():A(): "。
- 最后,类 D 的构造函数被调用,由于 D 类同时继承了类 C 和类 B,而这两个类都已经初始化过虚基类 A 的部分,所以在 D 的构造函数中不需要再次调用 A 的构造函数。
由于类D是多重继承体系中的最底层类,它同时继承了类C和类B,而这两个类都间接继承了虚基类A。在这种情况下,编译器会负责确保虚基类A只被初始化一次
■ C++ 指针
■ this 指针
this 指针是一个特殊的指针,它指向当前对象的实例。
每一个对象都能通过 this 指针来访问自己的地址。
友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。
■ 指向类的指针
示例一:声明和初始化指向类的指针
#include <iostream>
class MyClass {
public:
int data;
void display() {
std::cout << "Data: " << data << std::endl;
}
};
int main() {
// 创建类对象
MyClass obj;
obj.data = 42;
// 声明和初始化指向类的指针
MyClass *ptr = &obj;
// 通过指针访问成员变量
std::cout << "Data via pointer: " << ptr->data << std::endl;
// 通过指针调用成员函数
ptr->display();
return 0;
}
示例二:动态分配内存
#include <iostream>
class MyClass {
public:
int data;
void display() {
std::cout << "Data: " << data << std::endl;
}
};
int main() {
// 动态分配内存创建类对象
MyClass *ptr = new MyClass;
ptr->data = 42;
// 通过指针调用成员函数
ptr->display();
// 释放动态分配的内存
delete ptr;
return 0;
}
示例三:指向类的指针作为函数参数
#include <iostream>
class MyClass {
public:
int data;
void display() {
std::cout << "Data: " << data << std::endl;
}
};
// 函数接受指向类的指针作为参数
void processObject(MyClass *ptr) {
ptr->display();
}
int main() {
MyClass obj;
obj.data = 42;
// 将指向类的指针传递给函数
processObject(&obj);
return 0;
}
■ 内联函数
C++ 内联函数是通常与类一起使用。如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码,否则将会继续使用旧的函数。
如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline
内联函数作用:
引入内联函数的目的是为了解决程序中函数调用的效率问题。
程序在编译器编译的时候,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体进行替换,而对于其他的函数,都是在运行时候才被替代。这其实就是个空间代价换时间的节省。
内联函数确定:
- 内联那些包含循环或 switch 语句的函数常常是得不偿失 (除非在大多数情况下, 这些循环或 switch 语句从不被执行).
- 有些函数即使声明为内联的也不一定会被编译器内联, 比如虚函数和递归函数就不会被正常内联. 通常, 递归函数不应该声明成内联函数
示例一:声明内联函数 inline
#include <iostream>
using namespace std;
inline int Max(int x, int y)
{
return (x > y)? x : y;
}
// 程序的主函数
int main( )
{
cout << "Max (20,10): " << Max(20,10) << endl;
cout << "Max (0,200): " << Max(0,200) << endl;
cout << "Max (100,1010): " << Max(100,1010) << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Max (20,10): 20
Max (0,200): 200
Max (100,1010): 1010
■ 构造& 析构函数
■ 构造函数
示例一:无参构造
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
//使用声明
Line line(10.0);
示例二:使用初始化列表来初始化字段
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
上面的语法等同于如下语法:
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
■ 析构函数
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Object is being created
Length of line : 6
Object is being deleted
■ 拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
拷贝构造函数的最常见形式如下:
classname (const classname &obj) {
// 构造函数的主体
}
示例一 拷贝构造函数
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr; //要记得释放内存
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line(10);
display(line);
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line1(10); //调用构造函数
Line line2 = line1; // 这里也调用了拷贝构造函数
display(line1); // 这里也调用了拷贝构造函数
display(line2); // 这里也调用了拷贝构造函数
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
调用构造函数
调用拷贝构造函数并为指针 ptr 分配内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
调用拷贝构造函数并为指针 ptr 分配内存
line 大小 : 10
释放内存
释放内存
释放内存
■ 友元
■ 友元类
整个类及其所有成员都是友元。
■ 友元函数
类的友元函数是定义在类内部,但有权访问类的所有私有(private)成员和保护(protected)成员。
尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元函数没有 this 指针,因为友元不是类的成员,只有成员函数才有 this 指针。
示例一:在类定义中函数原型前使用关键字 friend,表示这个函数是类的友元函数,在函数中可以访问类中的成员函数或成员变量
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box ); //声明友元函数,但是友元函数并不是成员函数。
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Width of box : 10
■ C++ 函数重载
■ C++ 多态
多态按字面的意思就是多种形态.
当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
■ 静态多态,或静态链接
示例一:静态多态,或静态链接, 有时候这也被称为早绑定
调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
int area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area ()
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Parent class area :
Parent class area :
■ 动态链接,或后期绑定
在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。这种操作被称为动态链接,或后期绑定。
示例一:声明前放置关键字 virtual
*此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 shape 中 所以会调用各自的 area() 函数。
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area() //添加virtual变成虚函数
{
cout << "Parent class area :" <<endl;
return 0;
}
};
修改后,当编译和执行前面的实例代码时,它会产生以下结果:
Rectangle class area :
Triangle class area :
■ 虚函数
虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
■ 纯虚函数
基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
**
示例一:virtual int area() = 0; 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
// pure virtual function
virtual int area() = 0; //= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
};
■ C++ 标准流,文件流
■ C++ 异常处理
■ C++ 动态内存
malloc() 函数在 C 语言中就出现了,在 C++ 中仍然存在
new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。
动态分配,一维数组
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
动态分配,二维数组
int **array;
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m];
for( int i=0; i<m; i++ )
{
array[i] = new int [n];
}
//释放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
动态分配,三维数组
int ***array;
// 假定数组第一维为 m, 第二维为 n, 第三维为h
// 动态分配空间
array = new int **[m];
for( int i=0; i<m; i++ )
{
array[i] = new int *[n];
for( int j=0; j<n; j++ )
{
array[i][j] = new int [h];
}
}
//释放
for( int i=0; i<m; i++ )
{
for( int j=0; j<n; j++ )
{
delete[] array[i][j];
}
delete[] array[i];
}
delete[] array;
对象的动态内存分配
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
■ C++ 模板
■ 函数模板
示例一:
#include <iostream>
#include <string>
using namespace std;
template <typename T>
inline T const& Max (T const& a, T const& b)
{
return a < b ? b:a;
}
int main ()
{
int i = 39;
int j = 20;
cout << "Max(i, j): " << Max(i, j) << endl;
double f1 = 13.5;
double f2 = 20.7;
cout << "Max(f1, f2): " << Max(f1, f2) << endl;
string s1 = "Hello";
string s2 = "World";
cout << "Max(s1, s2): " << Max(s1, s2) << endl;
return 0;
}
当上面的代码被编译和执行时,它会产生下列结果:
Max(i, j): 39
Max(f1, f2): 20.7
Max(s1, s2): World
■ 类模板
示例一:类模板
#include <iostream>
#include <vector>
#include <cstdlib>
#include <string>
#include <stdexcept>
using namespace std;
template <class T>
class Stack {
private:
vector<T> elems; // 元素
public:
void push(T const&); // 入栈
void pop(); // 出栈
T top() const; // 返回栈顶元素
bool empty() const{ // 如果为空则返回真。
return elems.empty();
}
};
template <class T>
void Stack<T>::push (T const& elem)
{
// 追加传入元素的副本
elems.push_back(elem);
}
template <class T>
void Stack<T>::pop ()
{
if (elems.empty()) {
throw out_of_range("Stack<>::pop(): empty stack");
}
// 删除最后一个元素
elems.pop_back();
}
template <class T>
T Stack<T>::top () const
{
if (elems.empty()) {
throw out_of_range("Stack<>::top(): empty stack");
}
// 返回最后一个元素的副本
return elems.back();
}
int main()
{
try {
Stack<int> intStack; // int 类型的栈
Stack<string> stringStack; // string 类型的栈
// 操作 int 类型的栈
intStack.push(7);
cout << intStack.top() <<endl;
// 操作 string 类型的栈
stringStack.push("hello");
cout << stringStack.top() << std::endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const& ex) {
cerr << "Exception: " << ex.what() <<endl;
return -1;
}
}
它会产生下列结果:
7
hello
Exception: Stack<>::pop(): empty stack