油库7号

Last Day Of The Summer
 
健一 @ 2009-12-24 19:20







 
ajk @ 2009-11-18 14:12

容易令人迷惑的构造函数
 
5、使用explicit禁止隐式类型转换
在构造函数的前面加上explicit就可以避免构造函数被自动匹配为转换构造函数,从而避免了隐式类型转换。
比较下面两个例子:
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s1("ajk", 1);
    s1 = "Tony";
    cout << "Back in main()" << endl;
}
在这个例子中,构造函数没有加explicit属性,所以”Tony”这个字符串会自动去匹配转换构造函数,实现隐式转换。
输出结果为:
Constructing NEW student ajk, id is 1
Constructing NEW student Tony, id is 0
Assigned the student ASSIGNED FROM Tony, id is 0
Destructing the student Tony, id is 0
Back in main()
Destructing the student ASSIGNED FROM Tony, id is 0
 
下面的例子中,在构造函数加上了的explicit
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    explicit Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s1("ajk", 1);
    s1 = "Tony";     //Error,由于有explicit,所以不能进行隐式转换
    cout << "Back in main()" << endl;
}
构造函数被加上explicit属性后,就不能被自动匹配为转换构造函数,所以这里的字符串”Tony”不能进行隐式类型转换。类型不匹配导致编译不能通过。



 
ajk @ 2009-11-18 14:10

容易令人迷惑的构造函数
 
4、什么时候会产生无名对象
(1)独立的显式调用构造函数时。
int main(){
    Student("ajk", 1);
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student ajk, id is 1
Destructing the student ajk, id is 1
Back in main()
 
(2)显式调用构造函数(含拷贝构造函数)为一个已有对象赋值时。
int main(){
    Student s;
    s = Student("ajk", 1);
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 1
Assigned the student ASSIGNED FROM ajk, id is 1
Destructing the student ajk, id is 1
Back in main()
Destructing the student ASSIGNED FROM ajk, id is 1
 
在上面的例子中,名字s是已经存在的一个对象的名字,所以在赋值时新创建的对象就是一个“无名对象”,它的生命期仅维持在创建它的这条语句上,一离开这条语句就被析构。
再看一个使用拷贝构造函数的例子:
int main(){
    Student s;
    Student s1("ajk", 1);
    s = Student(s1);
    cout << "Back in main()" << endl;
}
    输出结果为
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 1
Constructing student COPY OF ajk, id is 1
Assigned the student ASSIGNED FROM COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Back in main()
Destructing the student ajk, id is 1
Destructing the student ASSIGNED FROM COPY OF ajk, id is 1
 
 
在这个地方要特别注意把赋值跟初始化区别开来。如果是初始化情况就不一样了。
比如:
int main(){
    Student s = Student("ajk", 1);
    cout << "Back in main()" << endl;
}
   
int main(){
    Student &s = Student("ajk", 1);
    cout << "Back in main()" << endl;
}
 
这两种情况都是初始化,输出结果为:
Constructing NEW student ajk, id is 1
Back in main()
Destructing the student ajk, id is 1
 
对于Student s = Student("ajk", 1)新创建对象的名字为s。有名字。
对于Student &s = Student("ajk", 1)新创建对象的别名是s。同样有名字。
所以他们的的生命期都要持续到main()函数结束。
 
(3)显式调用构造函数(含拷贝构造函数),并取其地址去初始化或赋值给指针。
int main(){
    Student *s = &Student("ajk", 1);
    s->GetInfo();
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student ajk, id is 1
Destructing the student ajk, id is 1
Get Info: The Student is 葺葺葺葺[1], id is 1
Back in main()
要注意,这种情况是非常危险的。
显式调用了构造函数,意味着这里会创建一个对象,但是此对象是一个“无名对象”。因为指针不同于引用,它只接收地址而不会成为初始化它的对象的别名。
创建了“无名对象”后,将其地址送给指针s。随着这条语句的结束,此“无名对象”的生命也到尽头,被析构。意味着指针s指向的空间被释放掉。所以后面在调用s->GetInfo()时,必然导致结果不正确。
类似的,再看一个拷贝构造函数的例子:
int main(){
    Student s1("ajk", 1);
    Student *s = &Student(s1);
    s->GetInfo();
    cout << "Back in main()" << endl;
}
输出结果如下:
Constructing NEW student ajk, id is 1
Constructing student COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Get Info: The Student is 葺葺葺葺葺葺葺葺, id is 1
Back in main()
Destructing the student ajk, id is 1



 
ajk @ 2009-11-18 14:07

容易令人迷惑的构造函数
 
3、对一个已有对象进行赋值
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
 
(1)
int main(){
    Student s;
    Student s1("ajk", 1);
 
    s = s1;
    cout << "Back in main()" << endl;
}
 
输出结果为:
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 1
Assigned the student ASSIGNED FROM ajk, id is 1
Back in main()
Destructing the student ajk, id is 1
Destructing the student ASSIGNED FROM ajk, id is 1
 
在类中重载了赋值操作符,所以对于
s = s1;
调用的是:
Student& operator=(Student const &s);
 
(2)
int main(){
    Student s;
    Student s1("ajk", 1);
 
    s = Student(s1);
    cout << "Back in main()" << endl;
}
 
输出结果为:
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 1
Constructing student COPY OF ajk, id is 1
Assigned the student ASSIGNED FROM COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Back in main()
Destructing the student ajk, id is 1
Destructing the student ASSIGNED FROM COPY OF ajk, id is 1
 
对于s = Student(s1);调用了拷贝构造函数意味着这里创建了新的对象。另外这里是赋值不是初始化,s是已有对象的名字。因此新创建的对象是一个“无名对象”。在赋值后,离开此语句“无名对象”就被析构。
不过,这里有个机制还不是很清楚,在vc8中:
s = Student(s1); s = Student(Student(s1)); 的执行效果是一样的,只做了一次拷贝构造。
s = Student(Student(Student(s1)));
s = Student(Student(Student(Student(s1)))); 的执行效果是一样的,做了两次拷贝构造。
… …
 
(3)
int main(){
    Student s;
   
    s = "ajk";
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 0
Assigned the student ASSIGNED FROM ajk, id is 0
Destructing the student ajk, id is 0
Back in main()
Destructing the student ASSIGNED FROM ajk, id is 0
 
    “ajk”会匹配转换构造函数进行隐式转换。同样,这里s是已有对象的名字,因此新创建的对象是“无名对象”。
 
(4)
int main(){
    Student s;
   
    s = Student("ajk", 1);
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student , id is 0
Constructing NEW student ajk, id is 1
Assigned the student ASSIGNED FROM ajk, id is 1
Destructing the student ajk, id is 1
Back in main()
Destructing the student ASSIGNED FROM ajk, id is 1
 
    同样,这里s是已有对象的名字,因此新创建的对象是“无名对象”。
在vc8中:
s = Student("ajk", 1); s = Student(Student("ajk", 1)); 的执行效果是一样的,只做了一次拷贝构造。
s = Student(Student(Student("ajk", 1)));
s = Student(Student(Student(Student("ajk", 1)))); 的执行效果是一样的,做了两次拷贝构造。
… …



 
ajk @ 2009-11-18 14:04

“她看出来了,不过,要等到后来所有真相都浮出水面时她才能明白这一切”——马克斯·朱萨克,《偷书贼


容易令人迷惑的构造函数
 
2、用一个对象去初始化另一个对象
2.1 初始化对象
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s("ajk", 1);
 
    Student s1(s);
    Student s2 = s;
    Student s3 = Student(s);
    //Student s4(Student(s));
    cout << "Back in main()" << endl;
}
在上面的例子中,用已有对象s分别去初始化s1、s2和s3。这个时候产生的输出如下:
Constructing NEW student ajk, id is 1
Constructing student COPY OF ajk, id is 1
Constructing student COPY OF ajk, id is 1
Constructing student COPY OF ajk, id is 1
Back in main()
Destructing the student COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Destructing the student ajk, id is 1
 
用一个已有的对象去初始化另一个新的对象,这个时候调用的是拷贝构造函数。
反汇编后的代码如下:
Student s("ajk", 1);
00411A8D push        1   
00411A8F push        offset string "ajk" (417788h)
00411A94 lea         ecx,[ebp-18h]
00411A97 call        Student::Student (4112BCh)
00411A9C mov         dword ptr [ebp-4],0
     Student s1(s);
00411AA3 lea         eax,[ebp-18h]
00411AA6 push        eax 
00411AA7 lea         ecx,[ebp-28h]
00411AAA call        Student::Student (4112ADh)
00411AAF mov         byte ptr [ebp-4],1
     Student s2 = s;
00411AB3 lea         eax,[ebp-18h]
00411AB6 push        eax 
00411AB7 lea         ecx,[ebp-38h]
00411ABA call        Student::Student (4112ADh)
00411ABF mov         byte ptr [ebp-4],2
     Student s3 = Student(s);
00411AC3 lea         eax,[ebp-18h]
00411AC6 push        eax 
00411AC7 lea         ecx,[ebp-48h]
00411ACA call        Student::Student (4112ADh)
00411ACF mov         byte ptr [ebp-4],3
下面分析一个代码片断,对于:
Student s1(s);
00411AA3 lea         eax,[ebp-18h]
00411AA6 push        eax 
00411AA7 lea         ecx,[ebp-28h]
00411AAA call        Student::Student (4112ADh)
00411AAF mov         byte ptr [ebp-4],1
这一段代码,是用已有的对象s去初始化新生成的对象s1。其中ebp-18h是对象s的首地址,ebp-28h是对象s1的首地址。对于本例子中的拷贝构造函数:
Student(Student const &s);
可以看到拷贝构造函数的参数是Student类型的引用,因此在这里得到对象s的首地址ebp-18h后,就将它用堆栈传递给拷贝构造函数。然后再将新对象s1的首地址通过寄存器ecx传递给拷贝构造函数。
这个地方需要注意的是,不能用Student s4(Student(s));这样的形式来初始化新对象。这种用法不会为对象s4分配空间,也不会调用任何的构造函数或者拷贝构造函数。VC8的编译器可以通过这条语句,但是什么都不会做。所以,要注意避免使用。
 
2.2 初始化引用
再看下面的例子,仍然是用一个已有对象来进行初始化,所不同的是这里用了引用
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s("ajk", 1);
 
    Student &s1(s);
    s1.GetInfo();
 
    Student &s2 = s;
    s2.GetInfo();
 
    Student &s3 = Student(s);
    s3.GetInfo();
    //Student &s4(Student(s)); 
    cout << "Back in main()" << endl;
}
产生的输出如下:
Constructing NEW student ajk, id is 1
Get Info: The Student is ajk, id is 1
Get Info: The Student is ajk, id is 1
Constructing student COPY OF ajk, id is 1
Get Info: The Student is COPY OF ajk, id is 1
Back in main()
Destructing the student COPY OF ajk, id is 1
Destructing the student ajk, id is 1
 
分析如下:
首先产生了对象s。然后用s来初始化Student类型的引用。
对于
Student &s1(s);
Student &s2 = s;
这两条语句的处理是完全一样的。编译器在处理时,就是把对象s的首地址传送给引用变量s1和s2所在的空间。在逻辑上s1和s2就是s的“别名”,使用名字s1和s2就如同使用名字s一样。所以对于s1和s2不会产生新的对象,他们使用的都是对象s。
需要特别注意的是s3
Student &s3 = Student(s);
在这里,符号=的右边Student(s)是显式调用了拷贝构造函数:
Student(Student const &s);
一旦有构造函数的调用,一定意味着有新的对象创建。
符号=的左边并没有用来存放新创建对象的空间,因为左边是一个Student类型的引用变量,引用变量只拥有一个双字空间,用来存放初始化它的对象的首地址。既然符号=的左边没有用来存放对象的空间,那就会在内存中为这个对象分配一个空间,然后用拷贝构造函数来初始化它,此对象生成后再将地址送给s3。所以,s3是新创建对象的“别名”。那么这个对象就是“有名对象”。因此它的生命期一直到main()函数结束时才结束。如果是无名对象那就是临时对象。
所以这里s1和s2都是s的别名,不会有新的对象产生。而s3却是新创建的一个对象的别名,且该对象在创建时是调用拷贝构造函数并使用了对象s来进行初始化。
 
2.3 初始化指针
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s("ajk", 1);
 
    Student *s1(&s);
    s1->GetInfo();
 
    Student *s2 = &s;
    s2->GetInfo();
 
    Student *s3 = &Student(s);
    s3->GetInfo();
 
    cout << "Back in main()" << endl;
}
输出结果如下:
Constructing NEW student ajk, id is 1
Get Info: The Student is ajk, id is 1
Get Info: The Student is ajk, id is 1
Constructing student COPY OF ajk, id is 1
Destructing the student COPY OF ajk, id is 1
Get Info: The Student is 葺葺葺葺葺葺葺葺, id is 1
Back in main()
Destructing the student ajk, id is 1
   
对于s1和s2接收到都是对象s的地址。不会创建新的对象。
需要特别注意的是s3。
Student *s3 = &Student(s);
调用了构造函数就意味着有新的对象创建。该对象在创建时使用了拷贝构造函数进行初始化。
s3是指针,指针与引用不同。指针只是接收地址而不会成为“别名”。所以这里新创建的对象就成为了“无名对象”。
“无名对象”的生命期仅持续于创建它的这条语句上。
一离开这条语句该“无名对象”就被析构。s3指向的空间就被释放掉。



 
ajk @ 2009-11-13 16:21

“每一次在飞机降落之后,我们刚刚才看清楚一片新土地,也才揭开这片土地的一点点秘密,不过,只有一点点。”——米夏,《飞越纳斯加之线
    
容易令人迷惑的构造函数
 
1、用初始值初始化对象
在创建一个对象时,始终要注意到有两件事情要发生:(1)分配空间,(2)初始化。这两件事情的发生有一个先后顺序,很显然,为对象分配空间发生在前,空间分配好后才针对该对象所分配到的空间进行初始化。所以在考虑对象的创建时就要想到:一、对象的空间在什么地方分配,生命期到什么位置结束;二、如何进行初始化,参数是怎么传递的。如果有显式调用构造函数,则表明对象空间一定已经分配。
在对对象进行初始化时,就是通过构造函数来完成初始化工作的。
为构造函数指定实参时有4种形式。
1.1 初始化对象
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student s1("ajk", 7);
    Student s2 = Student("Tony", 8);
    Student s3 = "Jack";
    Student s4(Student("Summer", 9));
    cout << "Back in main()" << endl;  
}
(1)
初始化就是要用同等类型的值来初始化新生成的对象。例如:
int i(5);
创建的对象i是int类型,用来初始化的值5也是int类型。
但在
Student s1("ajk", 7);
用来初始化对象s1的一个是字符串”ajk”,另一个是整型7。看起来并不是用同等类型的值来初始化新生成的对象。
实际上这个时候Student s1("ajk", 7)是等价于Student s1(Student("ajk", 7))的,通过构造函数来达到在逻辑上用同等类型的值来对对象进行初始化。
(2)
对于Student s2 = Student("Tony", 8);
这里的符号=表示初始化,而不是赋值。
另外需要注意的是在符号=的右边显示地出现了构造函数,在这里并不会额外生成临时对象s2的首地址会传递给构造函数,构造函数通过此地址直接去初始化s2所在的空间。。在调用构造函数时,对象
(3)
对于Student s3 = "Jack";
在这里符号=的右边是一个字符串,通过匹配会调用“转换构造函数”
Student(char const *ptrName = "", int id = 0);
也就是这里的Student s3 = "Jack";
等价于Student s3 = Student("Tony");
需要特别注意的是:
如果在初始化对象时要象这样Student s3 = "Jack";通过“转换构造函数”来完成,那么只能有一个参数,并且要有构造函数能匹配只有一个参数的情况。这个例子中,虽然此构造函数有两个参数,但是第二个参数id有默认值,所以可以匹配一个参数的情况。
如果不是只有一个参数将不能用此种形式。
例如
Student s3 = ("Jack", 6);       //error
这里企图带两个参数,但是在符号=的右边却构成了一个“逗号表达式”。
("Jack", 6)这个逗号表达式的值为6。
 
下面通过反汇编的代码可以看到,以上四种形式在调用构造函数时,反汇编的代码都是一模一样的。
 
int main(){
00411A50 push        ebp 
00411A51 mov         ebp,esp
00411A53 push        0FFFFFFFFh
00411A55 push        offset __ehhandler$_main (4155F0h)
00411A5A mov         eax,dword ptr fs:[00000000h]
00411A60 push        eax 
00411A61 sub         esp,100h
00411A67 push        ebx 
00411A68 push        esi 
00411A69 push        edi 
00411A6A lea         edi,[ebp-10Ch]
00411A70 mov         ecx,40h
00411A75 mov         eax,0CCCCCCCCh
00411A7A rep stos    dword ptr es:[edi]
00411A7C mov         eax,dword ptr [___security_cookie (419008h)]
00411A81 xor         eax,ebp
00411A83 push        eax 
00411A84 lea         eax,[ebp-0Ch]
00411A87 mov         dword ptr fs:[00000000h],eax
     Student s1("ajk", 7);
00411A8D push        7   
00411A8F push        offset string "ajk" (417758h)
00411A94 lea         ecx,[ebp-18h]
00411A97 call        Student::Student (41129Eh)
00411A9C mov         dword ptr [ebp-4],0
     Student s2 = Student("Tony", 8);
00411AA3 push        8   
00411AA5 push        offset string "Tony" (417B28h)
00411AAA lea         ecx,[ebp-28h]
00411AAD call        Student::Student (41129Eh)
00411AB2 mov         byte ptr [ebp-4],1
     Student s3 = Student("Jack");
00411AB6 push        0   
00411AB8 push        offset string "Jack" (417744h)
00411ABD lea         ecx,[ebp-38h]
00411AC0 call        Student::Student (41129Eh)
00411AC5 mov         byte ptr [ebp-4],2
     Student s4(Student("Summer", 9));
00411AC9 push        9   
00411ACB push        offset string "Summer" (41776Ch)
00411AD0 lea         ecx,[ebp-48h]
00411AD3 call        Student::Student (41129Eh)
00411AD8 mov         byte ptr [ebp-4],3
     cout << "Back in main()" << endl;   
 
比如,对于
Student s1("ajk", 7);
00411A8D push        7   
00411A8F push        offset string "ajk" (417758h)
00411A94 lea         ecx,[ebp-18h]
00411A97 call        Student::Student (41129Eh)
首先通过堆栈将参数7,和字符串”ajk”的首地址传送给构造函数。C/C++参数的入栈顺序是从右往左。然后,通过
lea         ecx,[ebp-18h]
将对象s1的首地址通过ecx传递给构造函数。
 
1.2 初始化引用
再看下面的例子,仍然是用初始值来进行初始化,所不同的是这里用了引用
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student &s1 = Student("ajk", 1);
    s1.GetInfo();
 
    Student &s2(Student("Tony", 2));
    s2.GetInfo();
 
    //Student &s3("Jack", 3);   ERROR
    //Student &s4 = "Summer";   ERROR
    cout << "Back in main()" << endl;  
}
 
首先了解引用在编译器中如何处理的。
    有如下代码:
int main(){
    int i = 9;
    int &ref_i = i;
    int *ptr_i = &i;
    int tmp = ref_i;
}
以下是反汇编后的代码:
int main(){
00411A50 push        ebp 
00411A51 mov         ebp,esp
00411A53 sub         esp,0F0h
00411A59 push        ebx 
00411A5A push        esi 
00411A5B push        edi 
00411A5C lea         edi,[ebp-0F0h]
00411A62 mov         ecx,3Ch
00411A67 mov         eax,0CCCCCCCCh
00411A6C rep stos    dword ptr es:[edi]
    
     int i = 9;
00411A6E mov         dword ptr [i],9
     int &ref_i = i;
00411A75 lea         eax,[i]
00411A78 mov         dword ptr [ref_i],eax
     int *ptr_i = &i;
00411A7B lea         eax,[i]
00411A7E mov         dword ptr [ptr_i],eax
 
     int tmp = ref_i;
00411A81 mov         eax,dword ptr [ref_i]
00411A84 mov         ecx,dword ptr [eax]
00411A86 mov         dword ptr [tmp],ecx
}
 
    以下是内存情况:
0x0012FF34 cc cc cc cc cc cc cc cc
0x0012FF3C 09 00 00 00 cc cc cc cc
0x0012FF44 cc cc cc cc 60 ff 12 00
0x0012FF4C cc cc cc cc cc cc cc cc
0x0012FF54 60 ff 12 00 cc cc cc cc
0x0012FF5C cc cc cc cc 09 00 00 00
0x0012FF64 cc cc cc cc b8 ff 12 00
 
首先,
int i = 9;
00411A6E mov         dword ptr [i],9
    为变量i分配的内存空间为首地址0012ff60H开始一个双字,在WIN32中int类型占32位。并初始化为值9。
    int &ref_i = i;
00411A75 lea         eax,[i]
00411A78 mov         dword ptr [ref_i],eax
    在定义引用类型的变量ref_i时,会首先给它分配一个双字空间,此双字空间的首地址在0012ff54H。在此双字空间中会用来盛放i的地址。所以,先用lea指令将变量i的地址0012ff60H提取到寄存器eax中。然后通过eax作为中转,将变量i的地址0012ff60H送到ref_i所在的双字空间[0012ff54H]中。
    int *ptr_i = &i;
00411A7B lea         eax,[i]
00411A7E mov         dword ptr [ptr_i],eax
从这里可以看出,引用类型和指针类型实质是一样的。
在定义指针类型的变量ptr_i时,会首先给它分配一个双字空间,此双字空间的首地址在0012ff48H。在此双字空间中会用来盛放i的地址。所以,先用lea指令将变量i的地址0012ff60H提取到寄存器eax中。然后通过eax作为中转,将变量i的地址0012ff60H送到ptr_i所在的双字空间[0012ff48H]中。
    int tmp = ref_i;
00411A81 mov         eax,dword ptr [ref_i]
00411A84 mov         ecx,dword ptr [eax]
00411A86 mov         dword ptr [tmp],ecx
首先通过直接寻址方式,将ref_i所在空间中的值(即双字空间[0012ff54H]中的值)0012ff60H送到寄存器eax中。
然后通过寄存器间接寻址方式,将双字空间[0012ff60H]中的值00000009H送到变量tmp所在的双字单元[0012ff3cH]中。
在使用引用的时候,需要注意:
(1)如果有
    int i = 9;
    int &ref_i = i;
则引用 ref_i 的值就是i的值为9
(2)对引用做取地址操作,取到的不是它自己的地址,而是初始化它的变量的地址值。
如果取变量i的地址 &i
&ref_i 取到的值仍然是 &i 。取到的就是变量i的地址值
即,引用对程序员是透明的。
取地址时的反汇编代码如下:
unsigned tmp1 = (unsigned)&ref_i;
00411A81 mov         eax,dword ptr [ref_i]
00411A84 mov         dword ptr [tmp1],eax
 
所以,引用在使用上跟指针是有很大区别的。
对于指针:
int *ptr_i = &i;
    (1)对指针变量取地址,取到的是指针变量本身的地址
&ptr_i 取到的是指针变量 ptr_i 本身的地址值。
(2)对以上定义有
ptr_i 里面盛放的值是 &i ,即变量i的地址值。
(3)*ptr_i 表示取出ptr_i所指向的内存单元里的值。即变量i的值。
 
对于
int main(){
    Student &s1 = Student("ajk", 1);
    s1.GetInfo();
 
    Student &s2(Student("Tony", 2));
    s2.GetInfo();
 
    //Student &s3("Jack", 3);   ERROR
    //Student &s4 = "Summer";   ERROR
    cout << "Back in main()" << endl;  
}
 
输出结果为:
Constructing NEW student ajk, id is 1
Get Info: The Student is ajk, id is 1
Constructing NEW student Summer, id is 2
Get Info: The Student is Summer, id is 2
Back in main()
Destructing the student Summer, id is 2
Destructing the student ajk, id is 1
 
反汇编代码如下:
Student &s1 = Student("ajk", 1);
00411A8D push        1   
00411A8F push        offset string "ajk" (41764Ch)
00411A94 lea         ecx,[ebp-24h]
00411A97 call        Student::Student (4112BCh)
00411A9C mov         dword ptr [ebp-4],0
00411AA3 lea         eax,[ebp-24h]
00411AA6 mov         dword ptr [ebp-14h],eax
     s1.GetInfo();
00411AA9 mov         ecx,dword ptr [ebp-14h]
00411AAC call        Student::GetInfo (411078h)
 
     Student &s2(Student("Summer", 2));
00411AB1 push        2   
00411AB3 push        offset string "Summer" (417758h)
00411AB8 lea         ecx,[ebp-40h]
00411ABB call        Student::Student (4112BCh)
00411AC0 mov         byte ptr [ebp-4],1
00411AC4 lea         eax,[ebp-40h]
00411AC7 mov         dword ptr [ebp-30h],eax
     s2.GetInfo();
00411ACA mov         ecx,dword ptr [ebp-30h]
00411ACD call        Student::GetInfo (411078h)
 
以下是内存情况:
0x0012FF24 cc cc cc cc e0 5f 3a 00
0x0012FF2C 02 00 00 00 cc cc cc cc
0x0012FF34 cc cc cc cc 28 ff 12 00
0x0012FF3C cc cc cc cc cc cc cc cc
0x0012FF44 08 5f 3a 00 01 00 00 00
0x0012FF4C cc cc cc cc cc cc cc cc
0x0012FF54 44 ff 12 00 cc cc cc cc
 
分析:
Student &s1 = Student("ajk", 1);
00411A8D push        1   
00411A8F push        offset string "ajk" (41764Ch)
00411A94 lea         ecx,[ebp-24h]
00411A97 call        Student::Student (4112BCh)
00411A9C mov         dword ptr [ebp-4],0
00411AA3 lea         eax,[ebp-24h]
00411AA6 mov         dword ptr [ebp-14h],eax
已知ebp=0012ff68H
在定义引用变量 s1 时,为其分配一个双字单元的空间。此空间首地址为ebp-14h = 0012ff54H。在初始化此引用时,是用Student类型的对象来进行初始化。这里有显式调用构造函数,事先在ebp-24h = 0012ff44H处为对象分配好空间。为对象分配空间时,是按其数据成员所占据的空间大小来分配的。Student类型的对象有两个数据成员:
    char *m_sPtrName;
    int m_id;
第一个是指针,用来存放姓名字符串的地址。在WIN32中指针是32位,占一个双字。
第二个是整型,在WIN32中整型是32位,占一个双字。
所以,Student类型的对象会占据两个双字。
00411A8D push        1   
00411A8F push        offset string "ajk" (41764Ch)
00411A94 lea         ecx,[ebp-24h]
00411A97 call        Student::Student (4112BCh)
通过堆栈传递构造函数的两个参数,参数入栈顺序为从右往左。
然后通过ecx传递此对象所在空间的首地址ebp-24H = 0012ff44H。
调用构造函数初始化此对象。
从0012ff44H处开始:第一个双字为char *m_sPtrName的值,为姓名字符串的首地址003a5f08H,第二个双字为int m_id的值,为ID的值1。
00411A9C mov         dword ptr [ebp-4],0
此处为对象计数器。
00411AA3 lea         eax,[ebp-24h]
00411AA6 mov         dword ptr [ebp-14h],eax
将对象的首地址0012ff44H送到引用变量Student &s1所在的单元[0012ff54H]处。
 
可以看到,这里均不会产生临时对象main()函数结束。。这里产生的对象生命期一直到
 
1.3 初始化指针
    再看下面的例子,仍然是用初始值来进行初始化,所不同的是这里用了指针
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = "", int id = 0):m_id(id){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Constructing student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
 
    ~Student(){
        cout << "Destructing the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        m_id = s.m_id;
        cout << "Assigned the student "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info: The Student is "
            << m_sPtrName
            << ", id is "
            << m_id << endl;
    }
private:
    char *m_sPtrName;
    int m_id;
};
 
Student Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
    return s;
}
 
int main(){
    Student *s1 = &Student("ajk", 1);
    s1->GetInfo();
 
    Student *s2(&Student("Tony", 2));
    s2->GetInfo();
 
    cout << "Back in main()" << endl;
}
输出结果为:
Constructing NEW student ajk, id is 1
Destructing the student ajk, id is 1
Get Info: The Student is 葺葺葺葺[1], id is 1
Constructing NEW student Tony, id is 2
Destructing the student Tony, id is 2
Get Info: The Student is 葺葺葺葺葺葺葺葺, id is 2
Back in main()
 
需要注意的是,这两个地方都产生了临时对象。对于:
Student *s1 = &Student("ajk", 1);
指针与引用最大的不同就在于指针不会成为初始化它的对象的“别名”,因此这个地方创建的对象就成为了“无名对象”。然后通过取地址操作将此“无名对象”的地址送给指针变量s1。
“无名对象”的生命期仅持续于创建它的这条语句上。
一但一离开这条语句,“无名对象”的生命也就到了尽头。所以,一离开这条语句后,马上就会对此“无名对象”进行析构。
因此指针s1所指向的空间就被释放掉。
对于s2也是类似的分析。



 
ajk @ 2009-11-05 17:27

一、构造函数
要掌握构造函数首先要了解两点:
1、对象是类的一个实例(instance)。
2、由类来定义对象时,应该做两件事:
一是为对象分配空间;
二是对其初始化。
这两件事按如下步骤来完成:
(1)按数据成员在类中的声明顺序,依次连续地对数据成员进行定义。所以对象所占的空间由数据成员决定,成员函数不占对象的空间。
在这里用了定义这个词,如果数据成员是普通类型(如int、char等),那就直接为数据成员分配空间;如果数据成员又是由类声明的对象,那在定义时就要按递归的思路,又按照这两个步骤来完成对它的定义。
    (2)调用我们自定义的构造函数中的代码来完成初始化。
程序员在编写一个类时,可以自定义构造函数,也可以不编写构造函数。如果程序员没有编写构造函数,那就认为这个类使用“由编译器提供的默认构造函数”(实际上就是没有构造函数)。所以,如果有自定义的构造函数,那以上两个步骤都得以做完;如果自己没有写构造函数,就只做了第一个步骤。
 
二、构造函数的格式
    (1)与类同名
    (2)无返回值,无返回类型,连void亦不能有,可有不带值的return语句
    (3)可以有参数,可以重载
 
默认构造函数
“默认构造函数”是指任何不带参数的构造函数,无论是由编译器提供还是自己编写的。自己编写时有两种形式:
(1)参数为空,或void;
(2)参数列表中的每个参数都有默认值。
 
需注意:
(1)一个类中不能有多于一个默认构造函数,否则编译器不知道该使用哪个;
(2)一旦有自定义的构造函数(无论是否为默认构造函数),则“编译器提供的默认构造函数”自动失效;
(3)由类生成其实例对象时,如果不带参数,则在类中一定要有默认构造函数。
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(int id){
        cout << "Constructing Student\n";
        m_sID = id;
    }
protected:
    int m_sID;
};
 
int main(){
    Student s1(1001);   //ok
    Student s2(1002);   //ok
    Student s3;         //error,没有默认构造函数与之匹配
}
 
三、析构函数
对象的生命期到尽头时,要将对象销毁。也有两件事要做:
一是清理工作;
二是释放对象所占的空间。
这两件事按如下步骤来完成:
(1)调用自定义的析构函数完成清理工作。
(2)按声明顺序的倒序依次销毁本对象定义的数据成员。
 
四、析构函数的格式
    (1)函数名为类名前加上~
     ~表示非,表示按位非,即相反之意,意为构造函数的逆。
    (2)无返回值,无返回类型,连void亦不能有,可有不带值的return语句
(3)无参数,无重载
 
五、实例
1、构造和析构的顺序
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(){
        cout << "Constructing Student\n";
    }
    ~Student(){
        cout << "Destructing Student\n";
    }
protected:
    int m_sID;
};
 
class Teacher{
public:
    Teacher(){
        cout << "Constructing Teacher\n";
    }
    ~Teacher(){
        cout << "Destructing Teacher\n";
    }
protected:
    int m_tID;
};
 
class TutorShip{
public:
    TutorShip (){
        cout << "Constructing Tutorship\n";
    }
    ~TutorShip(){
        cout << "Destructing Tutorship\n";
    }
protected:
    Student s1;
    Teacher t1;
};
 
void main(){
    TutorShip ts;
    cout << "Back in main()\n";
}
运行结果为:
Constructing Student
Constructing Teacher
Constructing Tutorship
Back in main()
Destructing Tutorship
Destructing Teacher
Destructing Student
 
2、带参数的构造函数
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = ""){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
    }
private:
    char *m_sPtrName;
};
 
int main(){
    Student s1("ajk");      //ok
    Student s2(NULL);       //ok
    Student s3;             //ok
}
 
3、构造函数可以重载,可以给参数默认值
(1)构造函数的重载
class Clock{
public:
    Clock();
    Clock(int h);
    Clock(int h, int m);
    Clock(int h, int m, int s);
private:
    int hours;
    int minutes;
    int seconds;
};
 
Clock::Clock(){
    hours = 9;
    minutes = 7;
    seconds = 0;
}
 
Clock::Clock(int h){
    hours = h;
    minutes = 7;
    seconds = 0;
}
 
Clock::Clock(int h, int m){
    hours = h;
    minutes = m;
    seconds = 0;
}
 
Clock::Clock(int h, int m, int s){
    hours = h;
    minutes = m;
    seconds = s;
}
int main(){
    Clock c1;
    Clock c2(10);
    Clock c3(10, 25);
    Clock c4(10, 25, 30);
}
重载是根据参数的不同来进行匹配的,参数的个数、顺序、类型。
 
(2)默认参数值
class Clock{
public:
    Clock(int h = 9, int m = 7, int s = 10);
private:
    int hours;
    int minutes;
    int seconds;
};
 
Clock::Clock(int h, int m, int s){
    hours = h;
    minutes = m;
    seconds = s;
}
int main(){
    Clock c1;               //ok
    Clock c2(10);           //ok
    Clock c3(10, 25);       //ok
    Clock c4(10, 25, 30);   //ok
    Clock c5(7, ,30);       //error
    Clock c6( , ,30);       //error
}
    在重载时,如果用的是匹配默认参数的形式,实参在匹配时是从左往右依次匹配,如Clock c3(10, 25);匹配后h10m25s10匹配时只能从左往右依次匹配,不能跳过前面的参数往后面匹配。
    另外,默认参数值,在函数既有声明又有定义的时候,一般放在声明的位置。
 
4、隐式类型转换(implicit conversions
所谓“隐式类型转换”是指不需要使用强制转换,编译器允许隐式地将一种类型转换到另一种类型。例如,int到double的转换是隐式的,从double到short的转换也是隐式的。事实上,任何C语言基本类型(不包括指针)都可以隐式地转换为其他基本类型。
例如:
void dummy(short i){}
int main(){
    double a = 3.4;
    dummy(a);       //ok, 隐式转换
}
 
对于用户自定义的类,如果要实现隐式转换就意味着编译器知道如何将其他类型转换为用户自定义类型,而不需要强制转换。编译器如何做到这一点呢?实际上就是通过暗中寻找类定义中的“转换构造函数”。
凡是可使用一个参数调用的构造函数,都可以当成是“转换构造函数”
例如:
class ADT{
public:
    ADT(char);
    ADT(int);
    ADT(double = 0.0);
    ADT(short, long = 0L);
};
如果有以上定义,则char可以隐式转换为ADT类型。类似的int、double、short都可以隐式转换为ADT类型。
例子:
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = ""){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName << endl;
    }
   
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        cout << "Constructing student "
            << m_sPtrName << endl;
    }
 
    ~Student(){
        cout << "Destructing "
            << m_sPtrName << endl;
        delete []m_sPtrName;
    }
 
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        cout << "Assigned the student "
            << m_sPtrName << endl;
        return *this;
    }
 
    void GetInfo(){
        cout << "Get Info. The name is "
            << m_sPtrName << endl;
    }
private:
    char *m_sPtrName;
};
 
void Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
}
 
int main(){
    cout << "Calling the Func()" << endl;
    Func("ajk");
    cout << "Back in main()" << endl;
}
在上面这个例子中,定义了可用一个参数调用的构造函数:
Student(char const *ptrName = "");
因此这个类具有“转换构造函数”。从定义看出,其可以实现从char const *类型到Student类型的“隐式类型转换”。也就是说,在需要使用Student类型的地方,如果用了char const *类型的对象,则该对象可自动转换成Student类型的对象
在上面的例子里,其中函数void Func(Student s);要求得到一个类型为Student的参数。在调用时,出现 Func("ajk"); 形式的调用。本来要求得到Student类型的参数,却得到了一个char const * 类型的"ajk"。所以,对象"ajk"会自动转换为Student类型的对象。
执行时,输出结果为:
Calling the Func()
Constructing NEW student ajk
In the Func()
Get Info. The name is ajk
Leaving the Func()
Destructing ajk
Back in main()
在调用Func("ajk")时,会生成一个临时对象。
在VC2005中进行调试,可打开反汇编:

(可将图保存到电脑,放大查看)
程序一开始有:
int main(){
00411740 push        ebp 
00411741 mov         ebp,esp
00411743 sub         esp,0D0h
00411749 push        ebx 
0041174A push        esi 
0041174B push        edi 
0041174C lea         edi,[ebp-0D0h]
00411752 mov         ecx,34h
00411757 mov         eax,0CCCCCCCCh
0041175C rep stos    dword ptr es:[edi]
    可见,一进入main函数后,首先在栈中为本函数分配了一段大小为D0h字节的空间,并将这D0h个字节空间中的每个字节都填充为值CCh。
    假设在程序进入main后,执行push ebp之前ebp=0012FFB8h,esp=0012FF6Ch
00411740 push        ebp 
00411741 mov         ebp,esp
00411743 sub         esp,0D0h
00411749 push        ebx 
0041174A push        esi 
0041174B push        edi
    执行后,ebp=0012FF68h,esp=0012FE8Ch,此时栈的情况如下图所示:
 
0012FE8C
edi
0012FE90h
esi
0012FE94h
ebx
0012FE98h
--
0012FF68h
0012FFB8h

    再执行:
0041174C lea         edi,[ebp-0D0h]
00411752 mov         ecx,34h
00411757 mov         eax,0CCCCCCCCh
0041175C rep stos    dword ptr es:[edi]
    再将地址从0012FE98h(ebp-D0h=0012FF68-D0h=0012FE98h)开始的34h个双字(34h个双字即34h*4=D0h个字节)全部填充为cccc cccch。
这段空间主要用来存放本函数内的局部变量/对象,也可用来传递参数等。
接下来,调用
    Func("ajk");
00411789 push        ecx  
0041178A mov         ecx,esp
0041178C mov         dword ptr [ebp-0C8h],esp
00411792 push        offset string "ajk" (417764h)
00411797 call        Student::Student (41119Ah)
0041179C mov         dword ptr [ebp-0D0h],eax
004117A2 call        Func (41113Bh)
004117A7 add         esp,4
注意到,在调用Func(“ajk”)时,首先将字符串常量”ajk”的首地址作为参数入栈,然后调用Student的构造函数来生成对象。
因为本来函数void Func(Student s)在参数上要求的是Student类型,但是传递给它的是字符串常量”ajk”的首地址,所以,这个地方就会进行“隐式类型转换”。通过查找,”ajk”可以匹配到构造函数Student(char const *ptrName = "")。所以,这里的构造函数Student(char const *ptrName = "")就是一个“转换构造函数”。
所以,这里的
00411797 call        Student::Student (41119Ah)
    调用的就是构造函数Student(char const *ptrName = "")
    call Student::Student (41119Ah) 这里就是发生一个转移,转移到地址41119Ah处执行。
Student::Student:
0041119A jmp         Student::Student (411820h)
41119Ah处再发生转移,转移到地址为411820h处执行。
从下图可以看到,411820h处就是构造函数Student(char const *ptrName = "")


构造函数生成对象后,最后通过eax寄存器返回生成的对象的首地址,对象的首地址存放在this对应到的双字单元中。


 this双字单元里的值为0012FE88h,即生成对象的首地址为0012FE88h:
 
 
this
0012FE88h
 
 
 
    mov eax, dword ptr[this] 这条指令就是把this这个符号地址所对应到的双字单元里的值送eax。执行后,送到eax里的值就是0012FE88h。就是我们新生成的对象的首地址。查看地址为0012FE88h的内存区域:


由类型Student生成的对象有一个数据成员,就是char *m_sPtrName;其类型为char*要占用一个双字。观察到其值为003a5f08h,这就是数据成员m_sPtrName的值,就是用new分配的用来存放学生姓名的内存空间的首地址。
再观察首地址为003a5f08h的内存单元:


可看到从003a5f08h开始有4个字节,其值分别是61h,6ah,6bh,00h,就是学生姓名”ajk”这个字符串。
在用构造函数生成对象后,其返回值eax里就是对象的首地址。将对象的首地址作为参数传递给函数Func(),并调用之。


在调用函数Func()将函数执行完后,返回调用点之前,Student生成的这个对象的生命期就结束。
void Func(Student s){
004114F0 push        ebp 
004114F1 mov         ebp,esp
004114F3 push        0FFFFFFFFh
004114F5 push        offset __ehhandler$?Func@@YAXVStudent@@@Z (415338h)
004114FA mov         eax,dword ptr fs:[00000000h]
00411500 push        eax 
00411501 sub         esp,0C0h
00411507 push        ebx 
00411508 push        esi 
00411509 push        edi 
0041150A lea         edi,[ebp-0CCh]
00411510 mov         ecx,30h
00411515 mov         eax,0CCCCCCCCh
0041151A rep stos    dword ptr es:[edi]
0041151C mov         eax,dword ptr [___security_cookie (419008h)]
00411521 xor         eax,ebp
00411523 push        eax 
00411524 lea         eax,[ebp-0Ch]
00411527 mov         dword ptr fs:[00000000h],eax
0041152D mov         dword ptr [ebp-4],0
     cout << "In the Func()" << endl;
00411534 mov         esi,esp
00411536 mov         eax,dword ptr [__imp_std::endl (41A34Ch)]
0041153B push        eax 
0041153C push        offset string "In the Func()" (417714h)
00411541 mov         ecx,dword ptr [__imp_std::cout (41A350h)]
00411547 push        ecx 
00411548 call        std::operator<<<std::char_traits<char> > (411172h)
0041154D add         esp,8
00411550 mov         ecx,eax
00411552 call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41A338h)]
00411558 cmp         esi,esp
0041155A call        @ILT+440(__RTC_CheckEsp) (4111BDh)
     s.GetInfo();
0041155F lea         ecx,[ebp+8]
00411562 call        Student::GetInfo (411078h)
     cout << "Leaving the Func()" << endl;
00411567 mov         esi,esp
00411569 mov         eax,dword ptr [__imp_std::endl (41A34Ch)]
0041156E push        eax 
0041156F push        offset string "Leaving the Func()" (4176FCh)
00411574 mov         ecx,dword ptr [__imp_std::cout (41A350h)]
0041157A push        ecx 
0041157B call        std::operator<<<std::char_traits<char> > (411172h)
00411580 add         esp,8
00411583 mov         ecx,eax
00411585 call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (41A338h)]
0041158B cmp         esi,esp
0041158D call        @ILT+440(__RTC_CheckEsp) (4111BDh)
}
00411592 mov         dword ptr [ebp-4],0FFFFFFFFh
00411599 lea         ecx,[ebp+8]
0041159C call        Student::~Student (41114Ah)  
004115A1 mov         ecx,dword ptr [ebp-0Ch]
004115A4  mov         dword ptr fs:[0],ecx
004115AB pop         ecx 
004115AC pop         edi 
004115AD pop         esi 
004115AE pop         ebx 
004115AF add         esp,0CCh
004115B5 cmp         ebp,esp
004115B7 call        @ILT+440(__RTC_CheckEsp) (4111BDh)
004115BC mov         esp,ebp
004115BE pop         ebp 
004115BF ret             
--- No source file -------------------------------------------------------------
 
    小结
对于
 
void Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
}
 
int main(){
    cout << "Calling the Func()" << endl;
    Func("ajk");
    cout << "Back in main()" << endl;
}
 
(1)”ajk”通过隐式类型转换,自动匹配构造函数生成Student的对象。
(2)在Func(“ajk”)中,”ajk”通过匹配的构造函数生成一个临时对象。
(3)在Func(“ajk”)中,”ajk”生成对象后,在传递给形式参数s时,并不作拷贝Func函数。也就是Func函数中的参数s就是”ajk”所生成的临时对象。。所以这里并不在生成对象后再调用拷贝构造函数,而是直接将所生成对象的首地址传递给
(4)在Func(“ajk”)调用完成后,”ajk”生成的对象就要销毁。
 
下面针对这个例子来看几种不同的情况,注意它们之间的区别。
(1)、
#define _CRT_SECURE_NO_DEPRECATE 1
//design by ajk
#include    <iostream>
using namespace::std;
 
class Student{
public:
    Student(char const *ptrName = ""){
        ptrName = ptrName ? ptrName:"";
        m_sPtrName = new char[strlen(ptrName)+1];
        strcpy(m_sPtrName, ptrName);
        cout << "Constructing NEW student "
            << m_sPtrName << endl;
    }
    Student(Student const &s){
        char *prefix = "COPY OF ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        m_sPtrName = new char[length];
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        cout << "Constructing student "
            << m_sPtrName << endl;
    }
    ~Student(){
        cout << "Destructing "
            << m_sPtrName << endl;
        delete []m_sPtrName;
    }
    Student& operator=(Student const &s){
        char *prefix = "ASSIGNED FROM ";
        size_t length = strlen(s.m_sPtrName) + strlen(prefix) + 1;
        if(length != strlen(m_sPtrName)+1){
            delete []m_sPtrName;
            m_sPtrName = new char[length];
        }
        strcpy(m_sPtrName, prefix);
        strcat(m_sPtrName, s.m_sPtrName);
        cout << "Assigned the student "
            << m_sPtrName << endl;
        return *this;
    }
    void GetInfo(){
        cout << "Get Info. The name is "
            << m_sPtrName << endl;
    }
private:
    char *m_sPtrName;
};
 
void Func(Student& s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
}
 
int main(){
    cout << "Calling the Func()" << endl;
    Func("ajk");      //error
    cout << "Back in main()" << endl;
}
这个例子不能通过编译,Func(Student& s)函数要求的参数是Student&类型。而在函数调用时,调用形式是Func("ajk")传递给函数的实际参数是”ajk”。又,由于没有构造函数可以最终返回Student&类型(构造函数在返回时,都是返回this里的值,也就是生成的对象的首地址),因此这里就不能进行“隐式类型转换”。
 
(2)、
void Func(Student s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
}
 
int main(){
    cout << "Calling the Func()" << endl;
    Func(Student("ajk"));
    cout << "Back in main()" << endl;
}
这个例子中,在调用Func(Student s)时,调用形式是Func(Student("ajk")),这里显示地调用了构造函数生成一个临时对象。并且在生成对象后,并不是将对象拷贝给Func函数的形式参数s,所以这里在生成对象后不会调用拷贝构造函数。而是将所生成对象的首地址,也就是this里的值,直接传递给Func函数。即Func函数中的参数s就是”ajk”所生成的临时对象。
程序执行后,输出为:
Calling the Func()
Constructing NEW student ajk
In the Func()
Get Info. The name is ajk
Leaving the Func()
Destructing ajk
Back in main()


通过反汇编,可以看到,在本例子中,
Func(Student("ajk"));
Func("ajk");
 是等价的。
 
    (3)、
void Func(Student& s){
    cout << "In the Func()" << endl;
    s.GetInfo();
    cout << "Leaving the Func()" << endl;
}
 
int main(){
    cout << "Calling the Func()" << endl;
    Func(Student("ajk"));
    cout << "Back in main()" << endl;
}
    执行后,输出为:
Calling the Func()
Constructing NEW student ajk
In the Func()
Get Info. The name is ajk
Leaving the Func()
Destructing ajk
Back in main()
    函数Func要求的参数为引用。在调用函数时,通过显示调用构造函数生成临时对象,并初始化形式参数s



 
ajk @ 2009-10-28 12:31

使用new时的初始化
在使用new分配内存单元时,一般希望新分配的单元能被初始化为全0。注意以下两条语句:
int *ptrInt1 = new int;         //新分配的内存单元里的值是未知
int *ptrInt2 = new int();       //加上括号。新分配的内存单元里的值是全0
 
例1:在局部区域内
void main(){
    int *ptrInt1 = new int;     //新分配的内存单元里的值是未知
    int *ptrInt2 = new int();   //新分配的内存单元里的值是全0
}
例2:在局部区域内,初始化为指定值
void main(){
    int *ptrInt1 = new int;                 //新分配的内存单元里的值是未知
    int *ptrInt2 = new int(0x12345678); //新分配的内存单元里的值为12345678H
}


在WIN32中,地址是一个无符号的双字,所以int* 类型的变量是一个双字大小的容器,里面用来装其所指向的内存单元的地址。从上图可以看出ptrInt1的值为003a4d08H,表示在用new int分配内存单元时,这个新的内存单元(其大小为一个双字,WIN32中int类型的单元要占32bit)的地址为003a4d08H。而由地址003a4d08H所指向的这个新分配的双字单元,里面的值为无意义的cdcdcdcdH。
    对于int *ptrInt2 = new int(0x12345678); 从上图可以看出ptrInt2的值为003a4d48H,且由地址003a4d48H所指向的这个新分配的双字单元,里面的值被初始化为12345678H。
    从下面的内存显示图中,可以看到[003a4d48H] = 12345678H。



    例3:在局部区域内,分配数组
void main(){
    int *ptrInt1 = new int[5];  //5个单元的值均未知(win32中,每个INT单元为一双字)
    int *ptrInt2 = new int[5](); //5个单元的值均为全0
}
例4:在全局区域内的情况跟在局部区域的以上情况都一样。(这跟普通的变量有区别)
 
int *ptrInt1 = new int[5];      //内存单元里的值未知
int *ptrInt2 = new int[5]();    //内存单元里的值均为0
 
void main(){
   
}
例5:如果是普通变量,那么在局部区域内的默认初始值是未知,而在全局区域内的的默认初始值是全0。
int var1[5];            //5个单元的值为全0
 
void main(){
    int var2[5];        //单元里的值未知
    int var3[5]={};     //通过这种方式可将单元里的值初始化为全0
                        //等价于
int var3[5]={0}
}



 
Locations of visitors to this page
日历
网志分类
『所有网志』
『汇编语言』
『C++』
『网络编程简介』
『 Win32程序设计』
『不专业』
最新留言
01/21 专业啊。。看不...
01/01 好像是歌的名字...
12/22 嗯,是呀 看来...
12/20 有看没有懂……...
站内搜索
友情链接
我的歪酷 非非共享界
◇油库8号
◇不可回收的Nick
◇夜色中的高潮
◇Try To Remember
◇那么蓝或者到处乱走
◇中国摄影
◇马未都
◇戴老师
◇戴老师2
◇橡树
◇cnpaf
◇kendiv
◇lu0
◇Sysinternals
◇forum of sysinternals
◇Mark Russinovich's blog
◇windowsitpro
◇codeguru
◇xplus
◇codeproject
◇钱晓捷
◇MSTechNet
◇电脑编程技巧与维护
◇phrack
◇rootkit
◇planet-lab
◇刘江@图灵
◇大宝
◇VCTeamBlog
◇三言二拍
◇C++的罗浮宫
◇麦田
◇木玛
◇阿牛
◇数学网
◇歌词服务小组
ACM
IEEE
Applicable Algebra in Engineering Communication and Computing
Stochastic Processes and Their Applications
Pacific Journal of Optimization
luojw
hust
p2p research
stanford
galeer
思绮
cdCoffee
yobo
尹隐于市
于坚
刘嘉楠
Leonard Cohen
中华第一剑
曹方
参考
内地控
eee4
沙发音乐
LOOKBOOK
周C
xunxun
C专题
vckbase
jensenh
订阅 RSS
0038121
歪酷博客