C++常用编程技术

[TOC]

C++常用编程技术

函数

  1. 函数定义
  2. 函数重载

C++允许同一函数名定义多个函数,但这些函数必须参数个数不同或类型不同,这就是函数重载

1
2
3
4
5
6
7
8
9
int  (int a, int b) {
return a < b ? a:b;
}
int min (long long a, long long b) {
return a < b ? a:b;
}
int min (int a, int b, int c) {

}
  1. 函数模板

函数模板建立一个通用函数,其函数类型和形参不具体指定而是用一个虚拟的类型来代表,这个通用函数就是模板。凡是函数体相同的函数都可以用模板代替,而不用定义多个函数,实际只需定义一次。调用函数时,系提供会根据实参的类型取代模板的虚类型。

1
2
3
4
5
6

using namespace std;
template<typename T>
T min(T a, T b) {
return a < b ? a:b;
}

数组

  1. 数组 a[10]
  2. 字符数组

strlensizeof

strlen()是函数,运行时计算。参数必须是字符型指针(char*),而且必须是以结尾当数组名作为参数传入时,其已经退化为指针了。

sizeof()是运算符,而不是一个函数,在编译时就计算好了,用于计算机数据空间的字节数。因此sizeof不能返回动态分配的内存大小,常用于返回类型和静态类型的分配对象,结构或数组所占空间。

  1. 数组–编译时分配数组空间大小
1
char a[10] = "hello";

由于char占1字节,所以sizeof(a)的值是10* 1 = 10 字节

  1. 指针–储存该指针占用空间大小
1
char *str = "I am from China"

在32位的编译器上,指针一律4字节

  1. 类型– 类型所占空间大小
1
int b = 10

32位机器上,int占4字节

  1. 对象–对象实际的占用空间
1
2
3
4
class class_Samlpe {
int a,b;
int func();
}class_a;
  1. 函数–函数返回类型所占空间大小

引用

引用作为函数参数:内存中没有产生实参的副本,而是对实参的直接操作。

常引用

如果想提高进程的效率,又要使传递给函数的数据不在函数中被改变,就应该使用常引用。

1
const 类型标识符 & 引用名 = 目标变量名;

这种方式声明的引用,不能通过对目标的变量值进行修改,保证了引用的安全

关于常引用典型的错误调用

1
2
3
4
5
6
string func1();
void func2(string &s);


func2(func1);
func2("hello");

原因在于,func1是函数返回值,”hello”是临时变量,二者都属于“临时变量”,C++中临时变量都是const类型的.因此上面试图将const转化为非const类型

引用类型应尽量被定义为const类型

结构体、共用体

结构体

共用体

共用体用关键字union定义,它是一种特殊的类,在一个共用体多种不同的数据类型,共享空间

Union 可以用来判断计算机的大小端

枚举

预处理

C++提供的预处理包括4中: 宏定义、文档包含、条件编译和布局控制

常用宏定义命令

#define 命令是一个宏定义命令,将标识符定义为一个字符串,该标识符被称为宏名

1
2
3
4
5
#define 宏名 字符串
#define PI 3.1415

#define 宏 (参数列表)宏
#define A(x) x

Do…while(0) 妙用

1
2
3
4
#define Foo(x) do{
statement one;
statement two;
}while(0)

条件编译

1
2
3
4
5
#ifdef 标识符
进程段1
#else
进程段2
#endif

extern C

面向对象

类与对象

面向对象的主要思想是把构成问题的各个事务分解成对象,建立对象的目的是藐视一个事务在解决问题中经过的步骤和行为。对象作为进程的基本单元,将进程和数据封装其中,以提高软件的重用性、灵活性和扩展性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CStudent {
public:
void display();
private:
int num;
char name[20];
int age;
};

void CStudent::display() {
cout<<"num:"<<num<<endl;
cout<<"name"<<name<<endl;
cout<<"age:"<<age<<endl;
}

类的封装性

C++中通过类实现封装性,把数据和数据有关的操作封装在一个类里。但是人们使用时往往不关心实现,为了实现类的封装性(数据隐藏、提供访问接口)类为成员提供了私有、公有和受保护三种基本的权限。

  1. 私有成员 private
  2. 公有成员 public
  3. 受保护成员 protect

构造函数

数据成员不能在类中初始化,而构造函数为此而生,主要用来处理数据成员的初始化,且不需要调用,建立对象自动执行。 构造函数必须与类名相同,不能随意命名,以便编译系统识别作为构造函数处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Time{
public:
Time();
set_time();
get_time();
private:
int hour, minute, second;
};
Time::Time(){
hour = 0
minite = 0
second = 0
}
// C++还提供一种参数初始化表的方法
Time::Time():hour(0), minite(0), second(0){}

构造函数不仅可以对数据成员赋值,也可以包含其他语句。如果用户没有定义构造函数 ,那么C++会自动为其生成一个构造函数,只是这个构造函数的函数体是空的。

构造函数可以被重载

调用构造函数时不必给出实参的构造函数,称为默认构造函数。无参的构造函数属于默认构造函数。一个类只能有一个默认构造函数。即使一个类中有多个构造函数,但建立对象时都只执行一个构造函数

构造函数可以添加默认参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Box{
public:
Box(int h=2,int w=2,int l=2);
int volume();
private:
int height,width,length;
}
Box::Box(int h,int w,int len){
height = h;
width = w;
len = length;
}
int Box::volume(){
return height*width*length;
}

Box box(1); // 显示指定第一个参数
Box box(1,2); // 显示指定1,2参数

使用默认参数的好处在于:调用构造函数时就算没有提供参数也不会出错,对每一个对象有相同的初始化状况。

一个类全是默认参数的构造函数后,就不能再重载构造函数了

析构函数

析构函数在函数名前加一个 ~ 它在对象的生命周期结束后自动执行

进程执行析构函数的时机有以下四种

  1. 如果函数定义了一个对象,这个函数调用结束时对象会被释放,且在对象释放前自动执行析构函数。
  2. static 局部对象在函数调用结束时对象不释放,所以也不执行析构函数,只有在main函数结束调用exit函数结束进程时,才调用static局部对象析构函数
  3. 全局对象则是在进程流离开其作用域(main函数结束或调用exit()函数时)才会执行全局对象的析构函数
  4. 用new建立的对象,用delete释放对象时,会调用对象的析构函数

析构函数的作用不是删除对象,而是在撤销对象占用的内存前完成一些清理工作,使得这些内存可以供新对象使用。析构函数的作用也不限于释放资源,它还可以被用来执行用户最后一次使用对象时执行的任何操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Box{
public:
Box(int h=2,int w=2,int l=2);
~Box(){
cout<<"Destructor called."<<endl;
}
int volume();
private:
int height,width,length;
}
Box::Box(int h,int w,int len){
height = h;
width = w;
len = length;
}
int Box::volume(){
return height*width*length;
}

静态数据成员

有时需要为某个类分类单一的储存空间,在C语言中可以使用全局变量,但这样很不安全(被修改),而且名字易冲突。如果可以把数据当成全局变量去储存,但又被隐藏在类内部,而且清楚的与这个类相联系,这种处理方法较为理想。这个可以用静态数据成员实现。类的静态数据成员共享静态储存空间,不管创建了多少这个类的对象,所有的对象静态数据共享这个储存空间,这就为对象之间提供了一种通信方法。静态数据属于类,他在类的范围内有效。

由于不管产生了多少对象, 类的静态数据都有单一的储存空间,所以储存空间必须在一个单一的地方。

静态数据成员必须出现在类的外部而且只能定义一次

1
2
3
4
5
6
7
// xx.h
class base{
public:
static int var;
}
// xx.cpp
int base::var = 10;

在头文档中定义(初始化)静态成员容易引起重复定义的错误,比如这个头文档被多个cpp包含的时候

C++静态数据成员被所有的类对象共享,包括该类的派生类对象。派生类对象与基类对象共享基类的静态数据成员。静态数据成员只占一份空间。如果改变它的值,则各个对象中这个数据成员的值都变了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base{
public:
static int var;
};
int Base::vat = 10;
class Derived:public Base{};
int main(){
Base base1;
base1.var++; //11
Base base2;
base2.var++; //12
Derived derived1;
derived1++; //13
Derived derived2;
derived2++; //14
return 0;
}

如果只声明类而未定义对象,类一般数据成员不占内存空间,只有在定义对象时才会为对象分配空间。但是静态数据成员不属于某一个类的对象,所以为对象分配的空间不包括静态数据成员所占的空间,静态数据成员在所有对象之外开辟的一段空间。

静态成员函数

与数据成员类似,成员函数也可以定义为静态,在函数前加入static关键字

与静态数据成员不同,静态成员函数是不是为了对象间的沟通,而是为了能处理静态数据成员。

当调用一个对象的成员函数时(非静态成员函数),系统会把该对象的起始地址付给this指针,而静态成员函数不属于某一对象,其没有this指针。既然他没有指向某一对象,也就无法对一个对象中的非静态成员进行默认访问。

静态成员函数与非静态成员函数的根本区别是:非静态成员函数有this指针,而静态成员函数没有。

静态成员函数可以直接引用本类中的静态数据成员,因为静态数据成员同样数据类

对象的储存空间

结论: 1 非静态成员变量总和 2 加上编译器为了CPU计算做出的数据对其处理和 3 支持虚函数所产生的负担总和。

  1. 空类的大小占用1字节
  2. 成员函数、构造函数、析构函数 不占用空间
  3. 编译器为了支持虚函数,会产生额外的负担,这正是指向虚表的指针大小,64位机器上占8字节。

this指针

每个对象中数据成员分别占有储存空间,如果对同一类定义了n个对象,则有n个同样大小的空间存放这些数据成员,不同对象引用数据成员时,提供了this指针

this指针有如下特点

  1. 只能在成员函数中使用,在全局函数、静态成员函数中都不能使用
  2. this指针成员金函数前构造,成员函数后清楚
  3. this指针会因为编译器不同而有不同的位置
  4. this是类的指针
  5. 因为this指针只有在成员函数中才能有意义,所以获得一个对象后,不能通过对象使用,也无法获取指针的位置
  6. 普通的类函数不会创建表来保存指针,只有虚函数会被放到函数表中

类模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 一个典型计算的类模板
template<class T>
class Operation {
public:
Operation (T a,T b):x(a),y(b){}
T add(){
return x+y;
}
T subtract(){
return x-y;
}
private:
T x,y;
}

// 类模板的成员函数在类外定义这么写
template<class T>
T Operation<T>::add(){
return x+y;
}

析构函数与构造函数的执行顺序

一般情况下,调用析构函数的次序与调用构造函数的次序相反:最先被调用的构造函数,其析构函数被最后调用;

最后调用的构造函数,其析构函数最先被调用

  1. 在全局范围中定义的对象(所有函数之外的对象)他的构造函数在文档所有函数之前。但是一个进程有多个文档,其不同文档的都定义了全局对象,这构造的顺序使不确定的。他们在main函数结束时析构
  2. 如果定义的是局部自动对象,建立对象时调用构造函数,如果函数被多次调用,则每次都调用构造函数
  3. 如果函数定义静态(static)局部对象,则只在第一次调用构造函数,在main函数结束后才析构

继承与派生

继承方式包括 public、private、protected,默认为private

  1. public 公用继承 共用成员、保护成员在派生类中保持访问属性
  2. private 私有继承,基类的公用、保护成员在派生类中称为private成员
  3. protect 受保护继承 基类共有、保护在派生类中成protected成员(保护成员的意思是 不能被外界引用,但是可以被派生类引用)

派生类包括,从基类继承而来的部分和声明派生类增加的部分

  1. 基类的成员函数只能访问基类的成员,不能访问派生类
  2. 派生类的成员函数可以访问基类、派生类的成员
  3. 派生类外可以方位基类成员、共有的成员

可以发现,无论哪一种继承方式,派生类不能访问基类的私有成员,私有成员只能被成员函数访问,毕竟派生类和基类不是一个类

派生类的构造与析构函数

派生类的基类数据成员与新增的数据成员共同组成,如果派生类新增成员包括其他子对象,派生类数据成员还间接的包括了这些对象的数据成员,因此派生类必须对这些数据成员初始化。

  1. 对基类成员和子对象成员的初始化必须在初始化列表中进行,新增的成员初始化既可以在初始列表,也可以在在构造函数体进行
  2. 派生类构造必须对这三类成员初始化1、调用基类构造函数 2、子对象的构造函书 3、派生类的构造函数体
  3. 派生类有多个基类时,同一层次的调用顺序取决于定义派生类时顺序(从左至右)
  4. 如果基类也是派生类、依次回溯

派生类构造函数与析构函数的调用顺序

1、基类构造函数 –》派生表中的顺序

2、成员类构造函数 –》 类中的声明顺序

3、 派生类构造函数 –》

类的多态

1.多态

C++中 多态是指不同功能函数可以用同一个函数名,这样一个函数名可以调用不同内容的函数,在面向对象中多态这么表述:向不同的对象发送同一个消息,不同的对象在接收时会产生不同的行为;也就是说,每个对象可以用自己的方式去相应共同的消息。

设想: 能否用同一个调用形式,既用派生类又能调用基类同名函数。

C++ 虚函数解决这个问题,虚函数允许派生类重新定义与基类同名的函数,并且可以通过基类指针访问基类和派生类中的同名函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Box{
public:
Box(int, int, int);
virtual void display();
protected:
int length, height, width;
}
Box::Box (int l, int h, int w) {
length = l;
height = h;
width = w;
}
void Box::display(){
cout<< ... << endl;
}
class FilledBox:public Box{
public:
FilledBox (int,int,int,int,string);
virtual void display();
private:
int weight;
string fruite;
};
void FilledBox::display(){
cout << ... <<nedl;
}
FilledBox::FilledBox(int l,int h, int w, int we, string f):
Box(l, h, w), weight(we), fruite(f){}
  1. 基类中使用virtual关键字声明成员函数为虚函数
  2. 派生类中重新定义此函数,要求函数名、函数类型、参数个数、类型全部与基类相同

C++规定当基类声明虚函数时,派生类中的同名函数自动成为虚函数,派生类定义该函数可加可不加virtual,但一般习惯每一层都加,这样进程更清晰

  1. 定义一个基类指针,使它指向统一类族中需要调用的对象
  2. 通过虚函数需基类指针配合使用,便可以调用同一类族中的同名函数

纯虚函数

纯虚函数是在基类中生命的函数,在基类中没有定义,但要求任何派生类都定义实现方法

1
virtual void function() = 0;

编译器要求派生类必须重载以实现多态性,同时含有纯虚函数的类称为抽象类

析构函数

C++ 中,构造函数不能被声明为虚函数 。然而析构函数可以被声明为虚函数。

如果想要通过父指针来销毁派生类,必须定义虚析构函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Base{
public:
Base(){cout<<'Base::Base()'<<endl;}
virtual ~Base(){cout<<'Base:~Base()'<<endl;}
}
class Derived::public Base{
public:
Derived(){cout<<'D'<<endl;}
~Derived(){cout<<'~D'<<endl;}
}

int main(){
Base* p = new Derived();
delete p;
return 0;
}

单例模式

设计思想: 定义了一个单例类,使用私有的静态指针指向唯一的实例,并通过共有的方法获取这个指针。

单例模式保证了进程运行的任何时刻,该单例类的实例只存在一个

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Sinst{
private:
Sinst(){};
static Sinst* pinst;
public:
static Sinst* getSinstance(){
if (pinst == NULL) {
pinst = new Sinst();
}
return pinst;
}
}

Sinst* Sinst::pinst = NULL;