C++类和对象学习总结

来源:互联网 发布:16年广东省的经济数据 编辑:程序博客网 时间:2024/06/05 16:09

C++中的类与对象

http://blog.csdn.net/qq_32583189/article/details/52412369

C++中的类与对象

C中的类与对象
定义类
创建头文件
类和结构体
访问控制
作用域解析运算符
实现类成员
默认的内联方法
创建对象
开发环境:Visual Studio 2010
定义类

通常,C++程序将接口(类定义)放在头文件中,并将实现(类方法的代码放在源代码文件中),本文章以一个学生的例子来完成代码。

学生定义:记录三门课程(语文,英语,数学)的成绩,能够查看学生的平均成绩
创建头文件

现在创建名为student.h的头文件
//student.h -- Student class interface//version 00#ifndef STUDENT_H_#define STUDENT_H_#include <string>class Student{ //class declarationprivate:    std::string name;    int ch;    int en;    int math;    float average;    void count(){        average = (ch + en + math+0.0F)/3;    }public:    Student();//构造函数    ~Student();//析构函数    void setName(std::string name);    void setChScore(int score);    void setEnScore(int score);    void setMathScore(int score);    void show();};#endif

注意:类定义和结构体定义一样,要在最后加上;(分号)

类和结构体

关键字class指明这些代码定义了一个类设计,在这里class和typename不是同义词,不能使用typename代替class

类描述看上去很像是包含成员函数以及public和private可见性标签的结构声明。实际上,C++对结构进行了扩展,使之具有相同的特性。它们之间唯一的区别就是,结构体的默认访问类型是public,而类为private。C++程序猿通常使用类来实现类的描述,而把结构体作为纯粹的数据对象表示。

访问控制

在C++程序中通常使用访问控制关键字来保证自身属性的可见性。常见的三个关键字为private,public,protected

关键字 含义
private C++中的默认访问权限,也就是说上述student.h文件中的private关键字可以省略。其含义表示为只有该对象自身可以访问这些属性或者方法,被private修饰的属性和方法对于外部来说不可见
public 公有权限,表示被public修饰的属性和方法可以被外部程序直接调用
protected 受保护的权限,表示这些属性和方法仅仅能被该类和派生类的内部访问,也就是说,对于外部世界来说,保护成员的行为与private相同,对于派生类来说,保护成员的行为和public相同
作用域解析运算符

在具体实现一个类之前,我们需要先了解一个运算符:作用域解析运算符(::)。该符号的作用是用来标识一个函数所属的类,例如当我们输入Student::average()时。表示我们访问的average()方法属于Student类而不是其他的类。 
但是在一个Student类的内部我们并不需要使用使用作用域解析运算符就能访问average()方法,这是因为在类的内部自身所定义的所有方法对自身来说都是可见的。 
对于一个方法名称来说,Student::average()是方法的函数限定名(全称),而average()方法属于非限定名(简称),当程序对于一个函数来源无异议时(没有重名方法),可以使用简称。

实现类成员

现在我们创建一个C++文件student.cpp

//student.cpp -- implementing the Student class
//version 00
#include "stdafx.h"
#include "student.h"

Student::Student(){
}
Student::~Student(){
}
void Student::setChScore(int score){
    Student::ch = score;
}
void Student::setName(std::string n){
    Student::name = n;
}
void Student::setEnScore(int score){
    en = score;
}
void Student::setMathScore(int score){
    math = score;

}


//不需要知道这个方法的具体实现,后面会介绍,本节重点是类与对象
void Student::show(){
    Student::count();
    ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);
    std::streamsize prec = cout.precision(1);
    cout<<name<<" 同学的语文成绩为"<<ch<<"分,数学成绩为"<<math<<"分,英语成绩为"<<en<<"分,平均成绩"<<average<<"分"<<endl;
    cout.setf(orig,ios_base::floatfield);
}

注意这个实现的Student::setChScore(int score)中我们在使用ch参数时使用了作用于解析运算符,其实在类的内部我们可以直接使用简称,所以在后续的方法中我们使用了非限定名。

默认的内联方法

我们在类的定义中声明并定义了一个count()方法,由于其位于声明中,编译器将其编译为内联方法。一般内容简单的方法都可以作为内联方法。如果我们并不想直接在定义中声明并定义一个内联方法,我们也可以在实现时给方法添加inline关键字。比如

    inline void count(){
        average = (ch + en + math+0.0F)/3;
    }

但是我们在声明外定义内联函数时必须注意C++中的一个规则:要求内联函数在每一个使用它的源文件中均由定义。也就是说我们需要在每一个使用该方法的文件中定义该方法,很明显我们并不想这样做,所以最好的实现就是在声明中实现定义方法。

创建对象

当我们在使用一个类的实例对象时需要用到一个关键字new,这个关键字和C语言中的malloc关键字类似,都是用来在堆内存中为数据分配一段空间。 
我们在main方法中举个例子

//visual studio 2010 -- main program
#include "stdafx.h"
#include "student.h"

int _tmain(int argc, _TCHAR* argv[]){
    Student *jack = new Student();
    jack->setName("jack");
    jack->setChScore(98);
    jack->setMathScore(100);
    jack->setEnScore(92);
    jack->show();
    delete jack;
    return 0;
}

输出结果:

jack 同学的语文成绩为98分,数学成绩为100分,英语成绩为92分,平均成绩96.7分
请按任意键继续. . .

温馨提醒:记得养成良好习惯,在对象不用后要使用delete关键字销毁对象。
========

C++类的定义和对象的创建

http://c.biancheng.net/cpp/biancheng/view/185.html

类和对象是 C++ 的重要特性,它们使得 C++ 成为面向对象的编程语言,可以用来开发中大型项目,本节重点讲解类和对象的语法,如果你对它们的概念还不了解,请先阅读《C++类和对象的概念》。

类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。
有些教程将类的成员变量称为类的属性(Property),将类的成员函数称为类的方法(Method)。在面向对象的编程语言中,经常把函数(Function)称为方法(Method)。
与结构体一样,类只是一种复杂数据类型的声明,不占用内存空间。而对象是类这种数据类型的一个变量,或者说是通过类这种数据类型创建出来的一份实实在在的数据,所以占用内存空间。
类的定义

类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。

一个简单的类的定义:
纯文本复制
class Student{
public:
    //成员变量
    char *name;
    int age;
    float score;
    //成员函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};
class是 C++ 中新增的关键字,专门用来定义类。Student是类的名称;类名的首字母一般大写,以和其他的标识符区分开。{ }内部是类所包含的成员变量和成员函数,它们统称为类的成员(Member);由{ }包围起来的部分有时也称为类体,和函数体的概念类似。public也是 C++ 的新增关键字,它只能用在类的定义中,表示类的成员变量或成员函数具有“公开”的访问权限,初学者请先忽略该关键字,我们将在《C++类成员的访问权限》中讲解。
注意在类定义的最后有一个分号;,它是类定义的一部分,表示类定义结束了,不能省略。
整体上讲,上面的代码创建了一个 Student 类,它包含了 3 个成员变量和 1 个成员函数。

类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。

类可以理解为一种新的数据类型,该数据类型的名称是 Student。与 char、int、float 等基本数据类型不同的是,Student 是一种复杂数据类型,可以包含基本类型,而且还有很多基本类型中没有的特性,以后大家会见到。
创建对象

有了 Student 类后,就可以通过它来创建对象了,例如:
Student liLei;  //创建对象
Student是类名,liLei是对象名。这和使用基本类型定义变量的形式类似:
int a;  //定义整型变量
从这个角度考虑,我们可以把 Student 看做一种新的数据类型,把 liLei 看做一个变量。

在创建对象时,class 关键字可要可不要,但是出于习惯我们通常会省略掉 class 关键字,例如:
class Student LiLei;  //正确
Student LiLei;  //同样正确
除了创建单个对象,还可以创建对象数组:
Student allStu[100];
该语句创建了一个 allStu 数组,它拥有100个元素,每个元素都是 Student 类型的对象。
访问类的成员

创建对象以后,可以使用点号.来访问成员变量和成员函数,这和通过结构体变量来访问它的成员类似,如下所示:
#include <iostream>
using namespace std;
//类通常定义在函数外面
class Student{
public:
    //类包含的变量
    char *name;
    int age;
    float score;
    //类包含的函数
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};
int main(){
    //创建对象
    Student stu;
    stu.name = "小明";
    stu.age = 15;
    stu.score = 92.5f;
    stu.say();
    return 0;
}
运行结果:
小明的年龄是15,成绩是92.5

stu 是一个对象,占用内存空间,可以对它的成员变量赋值,也可以读取它的成员变量。

类通常定义在函数外面,当然也可以定义在函数内部,不过很少这样使用。
使用对象指针

C语言中经典的指针在 C++ 中仍然广泛使用,尤其是指向对象的指针,没有它就不能实现某些功能。

上面代码中创建的对象 stu 在栈上分配内存,需要使用&获取它的地址,例如:
Student stu;
Student *pStu = &stu;
pStu 是一个指针,它指向 Student 类型的数据,也就是通过 Student 创建出来的对象。

当然,你也可以在堆上创建对象,这个时候就需要使用前面讲到的new关键字,例如:
Student *pStu = new Student;
在栈上创建出来的对象都有一个名字,比如 stu,使用指针指向它不是必须的。但是通过 new 创建出来的对象就不一样了,它在堆上分配内存,没有名字,只能得到一个指向它的指针,所以必须使用一个指针变量来接收这个指针,否则以后再也无法找到这个对象了,更没有办法使用它。也就是说,使用 new 在堆上创建出来的对象是匿名的,没法直接使用,必须要用一个指针指向它,再借助指针来访问它的成员变量或成员函数。

栈内存是程序自动管理的,不能使用 delete 删除在栈上创建的对象;堆内存由程序员管理,对象使用完毕后可以通过 delete 删除。在实际开发中,new 和 delete 往往成对出现,以保证及时删除不再使用的对象,防止无用内存堆积。
栈(Stack)和堆(Heap)是 C/C++ 程序员必须要了解的两个概念,我们已在《C语言和内存》专题中进行了深入讲解,相信你必将有所顿悟。
有了对象指针后,可以通过箭头->来访问对象的成员变量和成员函数,这和通过结构体指针来访问它的成员类似,请看下面的示例:
pStu -> name = "小明";
pStu -> age = 15;
pStu -> score = 92.5f;
pStu -> say();
下面是一个完整的例子:
#include <iostream>
using namespace std;
class Student{
public:
    char *name;
    int age;
    float score;
    void say(){
        cout<<name<<"的年龄是"<<age<<",成绩是"<<score<<endl;
    }
};
int main(){
    Student *pStu = new Student;
    pStu -> name = "小明";
    pStu -> age = 15;
    pStu -> score = 92.5f;
    pStu -> say();
    delete pStu;  //删除对象
    return 0;
}
运行结果:
小明的年龄是15,成绩是92.5

虽然在一般的程序中无视垃圾内存影响不大,但记得 delete 掉不再使用的对象依然是一种良好的编程习惯。
总结

本节重点讲解了两种创建对象的方式:一种是在栈上创建,形式和定义普通变量类似;另外一种是在堆上创建,必须要用一个指针指向它,读者要记得 delete 掉不再使用的对象。

通过对象名字访问成员使用点号.,通过对象指针访问成员使用箭头->,这和结构体非常类似。
========

C++类(Class)总结

http://www.cnblogs.com/xiongxuanwen/p/4290086.html
 
一、C++类的定义
    C++中使用关键字 class 来定义类, 其基本形式如下:
class 类名
{
public:
//行为或属性 
protected:
//行为或属性
private:
//行为或属性
};
 
示例:
     定义一个点(Point)类, 具有以下属性和方法:
      属性: x坐标, y坐标
      方法: 1.设置x,y的坐标值; 2.输出坐标的信息。
实现代码:
class Point
{
public:
     void setPoint(int x, int y);
     void printPoint();
 
private:
     int xPos;
     int yPos;
};  
代码说明:
     上段代码中定义了一个名为 Point 的类, 具有两个私密属性, int型的xPos和yPos, 分别用来表示x点和y点。
     在方法上, setPoint 用来设置属性, 也就是 xPos 和 yPos 的值; printPoint 用来输出点的信息。    


1 数据抽象和封装
     抽象是通过特定的实例抽取共同特征以后形成概念的过程。一个对象是现实世界中一个实体的抽象,一个类是一组对象的抽象。
     封装是将相关的概念组成一个单元,然后通过一个名称来引用它。面向对象封装是将数据和基于数据的操作封装成一个整体对象,对数据的访问或修改只能通过对象对外提供的接口进行。
 
2 类定义
     几个重要名词:
(1) 类名
     遵循一般的命名规则; 字母,数字和下划线组合,不要以数字开头。
(2) 类成员
     类可以没有成员,也可以定义多个成员。成员可以是数据、函数或类型别名。所有的成员都必须在类的内部声明。
     没有成员的类是空类,空类也占用空间。
class People
{
};
sizeof(People) = 1;    
(3) 构造函数
     构造函数是一个特殊的、与类同名的成员函数,用于给每个数据成员设置适当的初始值。
(4) 成员函数
     成员函数必须在类内部声明,可以在类内部定义,也可以在类外部定义。如果在类内部定义,就默认是内联函数。
 
3 类定义补充
3.1 可使用类型别名来简化类
     除了定义数据和函数成员之外,类还可以定义自己的局部类型名字。
     使用类型别名有很多好处,它让复杂的类型名字变得简单明了、易于理解和使用,还有助于程序员清楚地知道使用该类型的真实目的。
class People

public: 
     typedef std::string phonenum; //电话号码类型
 
     phonenum phonePub; //公开号码
private:      
     phonenum phonePri;//私人号码
}; 
 
3.2 成员函数可被重载
     可以有多个重载成员函数,个数不限。
3.3 内联函数
     有三种:
(1)直接在类内部定义。
(2)在类内部声明,加上inline关键字,在类外部定义。
(3)在类内部声明,在类外部定义,同时加上inline关键字。注意:此种情况下,内联函数的定义通常应该放在类定义的同一头文件中,而不是在源文件中。这是为了保证内联函数的定义在调用该函数的每个源文件中是可见的。
3.4 访问限制
     public,private,protected 为属性/方法限制的关键字。
3.5 类的数据成员中不能使用 auto、extern和register等进行修饰, 也不能在定义时进行初始化
     如 int xPos = 0; //错;
例外:
          静态常量整型(包括char,bool)数据成员可以直接在类的定义体中进行初始化,例如:
          static const int ia= 30; 
 
4 类声明与类定义
4.1 类声明(declare)
class Screen;
      在声明之后,定义之前,只知道Screen是一个类名,但不知道包含哪些成员。只能以有限方式使用它,不能定义该类型的对象,只能用于定义指向该类型的指针或引用,声明(不是定义)使用该类型作为形参类型或返回类型的函数。
void Test1(Screen& a){};
void Test1(Screen* a){};
4.2 类定义(define)
     在创建类的对象之前,必须完整的定义该类,而不只是声明类。所以,类不能具有自身类型的数据成员,但可以包含指向本类的指针或引用。
class LinkScreen
{
public:
          Screen window;
          LinkScreen* next;
          LinkScreen* prev;
}; //注意,分号不能丢
     因为在类定义之后可以接一个对象定义列表,可类比内置类型,定义必须以分号结束:
class LinkScreen{ /* ... */ };
class LinkScreen{ /* ... */ } scr1,scr2; 
     
5 类对象
     定义类对象时,将为其分配存储空间。
     Sales_item item; //编译器分配了足以容纳一个 Sales_item 对象的存储空间。item 指的就是那个存储空间。
 
6 隐含的 this 指针 
     成员函数具有一个附加的隐含形参,即 this指针,它由编译器隐含地定义。成员函数的函数体可以显式使用 this 指针。
6.1 何时使用 this 指针
     当我们需要将一个对象作为整体引用而不是引用对象的一个成员时。最常见的情况是在这样的函数中使用 this:该函数返回对调用该函数的对象的引用。
class Screen 
{
...
public:
      Screen& set(char);
};
Screen& Screen::set(char c) 
{
      contents[cursor] = c;
      return *this;
}


7 类作用域
     每个类都定义了自己的作用域和唯一的类型。
     类的作用域包括:类的内部(花括号之内), 定义在类外部的成员函数的参数表(小括号之内)和函数体(花括号之内)。
class Screen 

//类的内部
...
}; 
//类的外部
char Screen::get(index r, index c) const
{
     index row = r * width;      // compute the row location
     return contents[row + c];   // offset by c to fetch specified character

     注意:成员函数的返回类型不一定在类作用域中。可通过 类名::来判断是否是类的作用域,::之前不属于类的作用域,::之后属于类的作用域。例如
Screen:: 之前的返回类型就不在类的作用域,Screen:: 之后的函数名开始到函数体都是类的作用域。
class Screen 

public: 
     typedef std::string::size_type index; 
     index get_cursor() const; 
}; 
Screen::index Screen::get_cursor() const   //注意:index前面的Screen不能少

     return cursor; 

     该函数的返回类型是 index,这是在 Screen 类内部定义的一个类型名。在类作用域之外使用,必须用完全限定的类型名 Screen::index 来指定所需要的 index 是在类 Screen 中定义的名字。
 
 
二 构造函数
     构造函数是特殊的成员函数,用来保证每个对象的数据成员具有合适的初始值。
     构造函数名字与类名相同,不能指定返回类型(也不能定义返回类型为void),可以有0-n个形参。
     在创建类的对象时,编译器就运行一个构造函数。
 
1 构造函数可以重载
     可以为一个类声明的构造函数的数量没有限制,只要每个构造函数的形参表是唯一的。
class Sales_item;
{
public: 
     Sales_item(const std::string&); 
     Sales_item(std::istream&); 
     Sales_item(); //默认构造函数
}; 
 
2 构造函数自动执行 
     只要创建该类型的一个对象,编译器就运行一个构造函数:
Sales_item item1("0-201-54848-8");
Sales_item *p = new Sales_item(); 
     第一种情况下,运行接受一个 string 实参的构造函数,来初始化变量item1。
     第二种情况下,动态分配一个新的 Sales_item 对象,通过运行默认构造函数初始化该对象。
 
3 构造函数初始化式
     与其他函数一样,构造函数具有名字、形参表和函数体。
     与其他函数不同的是,构造函数可以包含一个构造函数初始化列表:  
Sales_item::Sales_item(const string &book): isbn(book), units_sold(0), revenue(0.0)
{ } 
     构造函数初始化列表以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在圆括号中的初始化式。
     构造函数可以定义在类的内部或外部。构造函数初始化只在构造函数的定义中指定。
     构造函数分两个阶段执行:(1)初始化阶段;(2)普通的计算阶段。初始化列表属于初始化阶段(1),构造函数函数体中的所有语句属于计算阶段(2)。
     初始化列表比构造函数体先执行。不管成员是否在构造函数初始化列表中显式初始化,类类型的数据成员总是在初始化阶段初始化。
3.1 哪种类需要初始化式
     const 对象或引用类型的对象,可以初始化,但不能对它们赋值,而且在开始执行构造函数的函数体之前要完成初始化。
     初始化 const 或引用类型数据成员的唯一机会是构造函数初始化列表中,在构造函数函数体中对它们赋值不起作用。
     没有默认构造函数的类类型的成员,以及 const 或引用类型的成员,必须在初始化列表中完成初始化。
class ConstRef 

public: 
     ConstRef(int ii); 
private: 
     int i; 
     const int ci; 
     int &ri; 
}; 
ConstRef::ConstRef(int ii) 
{
     i = ii;   // ok 
     ci = ii;  // error
     ri = i;   // 
}
     应该这么初始化:
ConstRef::ConstRef(int ii): i(ii), ci(i), ri(ii) { } 
3.2 成员初始化的次序
     每个成员在构造函数初始化列表中只能指定一次。重复初始化,编译器一般会有提示。
     成员被初始化的次序就是定义成员的次序,跟初始化列表中的顺序无关。
3.3 初始化式表达式
     初始化式可以是任意表达式
Sales_item(const std::string &book, int cnt, double price): isbn(book), units_sold(cnt), revenue(cnt * price) { }
3.4 类类型的数据成员的初始化式
     初始化类类型的成员时,要指定实参并传递给成员类型的一个构造函数,可以使用该类型的任意构造函数。
Sales_item(): isbn(10, '9'), units_sold(0), revenue(0.0) {}
 3.5 类对象的数据成员的初始化      
     在类A的构造函数初始化列表中没有显式提及的每个成员,使用与初始化变量相同的规则来进行初始化。
     类类型的数据成员,运行该类型的默认构造函数来初始化。
     内置或复合类型的成员的初始值依赖于该类对象的作用域:在局部作用域中不被初始化,在全局作用域中被初始化为0。假设有一个类A,
class A
{
    public:
        int ia;
        B b;
};
    A类对象A a;不管a在局部作用域还是全局作用域,b使用B类的默认构造函数来初始化,ia的初始化取决于a的作用域,a在局部作用域,ia不被初始化,a在全局作用域,ia初始化0。


4 默认构造函数 
     不含形参的构造函数就是默认构造函数。     
     只要定义一个对象时没有提供初始化式,就使用默认构造函数。如: A a;
     为所有形参提供默认实参的构造函数也定义了默认构造函数。例如:
class A
{
public: 
     A(int a=1,char c =''){}
private:  
     int ia;
     char c1;
};
4.1 合成的默认构造函数
     只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。
     一个类只要定义了一个构造函数,编译器也不会再生成默认构造函数。
建议:
     如果定义了其他构造函数,也提供一个默认构造函数。
     如果类包含内置或复合类型(如 int& 或 string*)的成员,它应该定义自己的构造函数来初始化这些成员。每个构造函数应该为每个内置或复合类型的成员提供初始化。
 
5 隐式类类型转换
5.1 只含单个形参的构造函数能够实现从形参类型到该类类型的一个隐式转换
class A
{
public:
     A(int a)
     {
          ia =a;
     }
 
     bool EqualTo(const A& a)
     {
          return ia == a.ia;
     }
 
private:
     int ia;
};
 
A a(1);
bool bEq = false;
bEq = a.EqualTo(1);//参数为1,实现从int型到A的隐式转换
 
5.2抑制由构造函数定义的隐式转换
     通过将构造函数声明为 explicit,来防止在需要隐式转换的上下文中使用构造函数: 
class A
{
public:
     explicit A(int a )
     {
          ia =a;
     }
 
     bool EqualTo(const A& a)
     {
          return ia == a.ia;
     }
 
private:
     int ia;
};
     通常,除非有明显的理由想要定义隐式转换,否则,单形参构造函数应该为 explicit。将构造函数设置为 explicit 可以避免错误。
 
 
三 复制控制
1 复制构造函数
1.1 几个要点
(1) 复制构造函数
     复制构造函数是一种特殊构造函数,只有1个形参,该形参(常用 const &修饰)是对该类类型的引用。
class Peopel
{
public:
     Peopel();//默认构造函数
     Peopel(const Peopel&);//复制构造函数
     ~Peopel();//析构函数
};
     当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。
Peopel a1; Peopel a2 = a1;
     当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。
Peopel Func(Peopel b){...}
(2)析构函数
     析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。
     析构函数可用于释放构造对象时或在对象的生命期中所获取的资源。
     不管类是否定义了自己的析构函数,编译器都自动执行类中非 static 数据成员的析构函数。
(3) 复制控制
     复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
(4) 两种初始化形式
     C++ 支持两种初始化形式:直接初始化和复制初始化。直接初始化将初始化式放在圆括号中,复制初始化使用 = 符号。
     对于内置类型,例如int, double等,直接初始化和复制初始化没有区别。
     对于类类型:直接初始化直接调用与实参匹配的构造函数;复制初始化先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。直接初始化比复制初始化更快。
(5)形参和返回值
     当形参或返回值为类类型时,由该类的复制构造函数进行复制。 
(6)初始化容器元素
     复制构造函数可用于初始化顺序容器中的元素。例如:
vector<string> svec(5);
     编译器首先使用 string 默认构造函数创建一个临时值,然后使用复制构造函数将临时值复制到 svec 的每个元素。 
(7)构造函数与数组元素
     如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。
     如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
Sales_item primer_eds[] = { string("0-201-16487-6"),
                                 string("0-201-54848-8"),
                                 string("0-201-82470-1"),
                                 Sales_item()
                               };


1.2 合成的复制构造函数
(1)合成的复制构造函数
     如果没有定义复制构造函数,编译器就会为我们合成一个。
     合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
逐个成员初始化:合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
例外:如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。


1.3 定义自己的复制构造函数
(1) 只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。 
class Peopel
{
public:
     std::string name;
     unsigned int id;
     unsigned int age;
     std::string address;
};
(2) 有些类必须对复制对象时发生的事情加以控制。
     例如,类有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义自己的复制构造函数。
     最好显式或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,必须定义默认构造函数。
 
1.4 禁止复制
     有些类需要完全禁止复制。例如,iostream 类就不允许复制。延伸:容器内元素不能为iostream 
     为了防止复制,类必须显式声明其复制构造函数为 private。


2 赋值操作符
     与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。
(1)重载赋值操作符
Sales_item& operator=(const Sales_item &);
(2)合成赋值操作符
     合成赋值操作符会逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。
(3)复制和赋值常一起使用 
     一般而言,如果类需要复制构造函数,它也会需要赋值操作符。 


3 析构函数
     构造函数的用途之一是自动获取资源;与之相对的是,析构函数的用途之一是回收资源。除此之外,析构函数可以执行任意类设计者希望在该类对象的使用完毕之后执行的操作。
(1) 何时调用析构函数
撤销(销毁)类对象时会自动调用析构函数。
变量(类对象)在超出作用域时应该自动撤销(销毁)。
动态分配的对象(new A)只有在指向该对象的指针被删除时才撤销(销毁)。
撤销(销毁)一个容器(不管是标准库容器还是内置数组)时,也会运行容器中的类类型元素的析构函数(容器中的元素总是从后往前撤销)。
(2)何时编写显式析构函数
     如果类需要定义析构函数,则它也需要定义赋值操作符和复制构造函数,这个规则常称为三法则:如果类需要析构函数,则需要所有这三个复制控制成员。
(3)合成析构函数
     合成析构函数按对象创建时的逆序撤销每个非 static 成员,因此,它按成员在类中声明次序的逆序撤销成员。
     对于每个类类型的成员,合成析构函数调用该成员的析构函数来撤销对象。
     合成析构函数并不删除指针成员所指向的对象。 所以,如果有指针成员,一定要定义自己的析构函数来删除指针。


     析构函数与复制构造函数或赋值操作符之间的一个重要区别:即使我们编写了自己的析构函数,合成析构函数仍然运行。
 
 
四 友元
     友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
     友元可以出现在类定义的内部的任何地方。
     友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。
     建议:将友元声明成组地放在类定义的开始或结尾。
 
1 友元类
class Husband
{
public:
     friend class Wife;
private:
     double money;//钱是老公私有的,别人不能动,但老婆除外
};
 
class Wife
{
public:
     void Consume(Husband& h)
     {
          h.money -= 10000;//老婆可以花老公的钱
     }
};
 
Husband h;
Wife w;
w.Consume(h);
 
2 使其他类的成员函数成为友元
class Husband; //1.声明Husband 
 
class Wife //2.定义Wife类 
{
public:
     void Consume(Husband& h);
};
 
class Husband //3.定义Husband类
{
public:
     friend void Wife::Consume(Husband& h);//声明Consume函数。
private:
     double money;//钱是老公私有的,别人不能动,但老婆除外
};
 
void Wife::Consume(Husband& h) //4.定义Consume函数。
{
     h.money -= 10000;//老婆可以花老公的钱
}
注意类和函数的声明和定义的顺序:
(1)声明类Husband 
(2)定义类Wife,声明Consume函数
(3)定义类Husband
(4)定义Consume函数。
 
 
五 static 类成员
 
static 成员,有全局对象的作用,但又不破坏封装。
1 static 成员变量
static 数据成员是与类关联的对象,并不与该类的对象相关联。
static 成员遵循正常的公有/私有访问规则。  


2 使用 static 成员而不是全局对象有三个优点。
(1)  static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2)  可以实施封装。static 成员可以是私有成员,而全局对象不可以。
(3)  通过阅读程序容易看出 static 成员是与特定类关联的,这种可见性可清晰地显示程序员的意图。 


3 static 成员函数
     在类的内部声明函数时需要添加static关键字,但是在类外部定义函数时就不需要了。
     因为static 成员是类的组成部分但不是任何对象的组成部分,所以有以下几个特点:
1) static 函数没有 this 指针
2) static 成员函数不能被声明为 const (将成员函数声明为 const 就是承诺不会修改该函数所属的对象)
3) static 成员函数也不能被声明为虚函数


4 static 数据成员 
     static 数据成员可以声明为任意类型,可以是常量、引用、数组、类类型,等等。
     static 数据成员必须在类定义体的外部定义(正好一次),并且应该在定义时进行初始化。
建议:定义在类的源文件中名,即与类的非内联函数的定义同一个文件中。注意,定义时也要带上类类型+"::"
double Account::interestRate = 0.035; 


5 特殊的静态常量整型成员 
     静态常量整型数据成员可以直接在类的定义体中进行初始化,例如:
static const int period = 30; 
     当然char 可以转换成整形,也是可以的,   static const char bkground = '#';
 
6 其他
(1)static 数据成员的类型可以是该成员所属的类类型。非 static 成员只能是自身类对象的指针或引用 
class Screen 
{
public:
         // ...
private:
         static Screen src1; // ok
         Screen *src2;       // ok
         Screen src3;        // error
}; 
(2)非 static 数据成员不能用作默认实参,static 数据成员可用作默认实参
class Screen 
{
public:
          Screen& clear(char = bkground);
private:
         static const char bkground = '#';//static const整形变量可以在类内部初始化。
};

========

C++创建对象的三种方式

http://blog.csdn.net/azhexg/article/details/14225545


C++中有三种创建对象的方法


#include <iostream>  
using namespace std;  
class A  
{  
private:  
    int n;  
public:  
    A(int m):n(m)  
    { }  
    ~A(){}  
};  
int main()  
{  
    A a(1);  //栈中分配  
    A b = A(1);  //栈中分配  
    A* c = new A(1);  //堆中分配  
  delete c;  
    return 0;  
}  


第一种和第二种没什么区别,一个隐式调用,一个显式调用,两者都是在进程虚拟地址空间中的栈中分配内存,而第三种使用了new,在堆中分配了内存,而栈中内存的分配和释放是由系统管理,而堆中内存的分配和释放必须由程序员手动释放。采用第三种方式时,必须注意一下几点问题:


new创建类对象需要指针接收,一处初始化,多处使用
new创建类对象使用完需delete销毁
new创建对象直接使用堆空间,而局部不用new定义类对象则使用栈空间
new对象指针用途广泛,比如作为函数返回值、函数参数等
频繁调用场合并不适合new,就像new申请和释放内存一样
栈的大小远小于堆的大
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率 比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在 堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会 分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
========

C++用new来创建对象和非new来创建对象的区别

http://www.cnblogs.com/GODYCA/archive/2013/01/10/2854777.html
我们都知道C++中有三种创建对象的方法,如下:


#include <iostream>
using namespace std;


class A
{
private:
    int n;
public:
    A(int m):n(m)
    {
    }
    ~A(){}
};


int main()
{
    A a(1);  //栈中分配
    A b = A(1);  //栈中分配
    A* c = new A(1);  //堆中分配
  delete c;
    return 0;
}


第一种和第二种没什么区别,一个隐式调用,一个显式调用,两者都是在进程虚拟地址空间中的栈中分配内存,而第三种使用了new,在堆中分配了内存,而栈中内存的分配和释放是由系统管理,而堆中内存的分配和释放必须由程序员手动释放,所以这就产生一个问题是把对象放在栈中还是放在堆中的问题,这个问题又和堆和栈本身的区别有关:


这里面有几个问题:


1.堆和栈最大可分配的内存的大小


2.堆和栈的内存管理方式


3.堆和栈的分配效率


首先针对第一个问题,一般来说对于一个进程栈的大小远远小于堆的大小,在linux中,你可以使用ulimit -s (单位kb)来查看一个进程栈的最大可分配大小,一般来说不超过8M,有的甚至不超过2M,不过这个可以设置,而对于堆你会发现,针对一个进程堆的最大可分配的大小在G的数量级上,不同系统可能不一样,比如32位系统最大不超过2G,而64为系统最大不超过4G,所以当你需要一个分配的大小的内存时,请用new,即用堆。


其次针对第二个问题,栈是系统数据结构,对于进程/线程是唯一的,它的分配与释放由操作系统来维护,不需要开发者来管理。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时,这些存储单元会被自动释放。栈内存分配运算内置于处理器的指令集中,效率很高,不同的操作系统对栈都有一定的限制。 堆上的内存分配,亦称动态内存分配。程序在运行的期间用malloc申请的内存,这部分内存由程序员自己负责管理,其生存期由开发者决定:在何时分配,分配多少,并在何时用free来释放该内存。这是唯一可以由开发者参与管理的内存。使用的好坏直接决定系统的性能和稳定。


由上可知,但我们需要的内存很少,你又能确定你到底需要多少内存时,请用栈。而当你需要在运行时才知道你到底需要多少内存时,请用堆。


最后针对第三个问题,栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率 比较高。堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法(具体的算法可以参考数据结构/操作系统)在 堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会 分 到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。


由上可知,能用栈则用栈。


复制代码
#include <stdio.h>
#include <stdlib.h>  
void main()
{
 int n,*p,i,j,m;
 printf("本程序可对任意个整数排序;\n");
 printf("请输入整数的总个数: ");
 scanf("%d",&n);
 p=(int *)calloc(n,sizeof(int));    //运行时决定内存分配大小
 if(p==0)   {
  printf("分配失败!\n");  
  exit(1);  
 }
========
0 0