C++ 教程 在线

2009cpp-friend-functions

对教程中的例子,稍加修改,添加了友元类的使用。

#include <iostream>
using namespace std;
class Box
{
    double width;
public:
    friend void printWidth(Box box);
    friend class BigBox;
    void setWidth(double wid);
};
class BigBox
{
public :
    void Print(int width, Box &box)
    {
        // BigBox是Box的友元类,它可以直接访问Box类的任何成员
        box.setWidth(width);
        cout << "Width of box : " << box.width << endl;
    }
};
// 成员函数定义
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;
    BigBox big;
    // 使用成员函数设置宽度
    box.setWidth(10.0);
    // 使用友元函数输出宽度
    printWidth(box);
    // 使用友元类中的方法设置宽度
    big.Print(20, box);
    getchar();
    return 0;
}

2008cpp-friend-functions

友元函数的使用

因为友元函数没有this指针,则参数要有三种情况: 

要访问非static成员时,需要对象做参数;

要访问static成员或全局变量时,则不需要对象做参数;

如果做参数的对象是全局对象,则不需要对象做参数.

可以直接调用友元函数,不需要通过对象或指针

实例代码:

class INTEGER
{
    friend void Print(const INTEGER& obj);//声明友元函数
};
void Print(const INTEGER& obj)
{
    //函数体
}
void main()
{
    INTEGER obj;
    Print(obj);//直接调用
}

2007cpp-copy-constructor

拷贝构造函数

几个原则:

C++ primer p406 :拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用拷贝构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。

C++支持两种初始化形式:

拷贝初始化 int a = 5; 和直接初始化 int a(5); 对于其他类型没有什么区别,对于类类型直接初始化直接调用实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数,也就是说:

A x(2);  //直接初始化,调用构造函数
A y = x;  //拷贝初始化,调用拷贝构造函数

必须定义拷贝构造函数的情况:

只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。

什么情况使用拷贝构造函数:

类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:

  • (1)一个对象以值传递的方式传入函数体
  • (2)一个对象以值传递的方式从函数返回
  • (3)一个对象需要通过另外一个对象进行初始化。

2006cpp-copy-constructor

拷贝构造函数的调用时机

在C++中,下面三种对象需要调用拷贝构造函数!

1. 对象以值传递的方式传入函数参数

class CExample 
{
private:
 int a;
public:
 //构造函数
 CExample(int b)
 { 
  a = b;
  cout<<"creat: "<<a<<endl;
 }
 //拷贝构造
 CExample(const CExample& C)
 {
  a = C.a;
  cout<<"copy"<<endl;
 }
 
 //析构函数
 ~CExample()
 {
  cout<< "delete: "<<a<<endl;
 }
     void Show ()
 {
         cout<<a<<endl;
     }
};
//全局函数,传入的是对象
void g_Fun(CExample C)
{
 cout<<"test"<<endl;
}
int main()
{
 CExample test(1);
 //传入对象
 g_Fun(test);
 return 0;
}

调用g_Fun()时,会产生以下几个重要步骤:

  • (1).test对象传入形参时,会先会产生一个临时变量,就叫 C 吧。
  • (2).然后调用拷贝构造函数把test的值给C。 整个这两个步骤有点像:CExample C(test);
  • (3).等g_Fun()执行完后, 析构掉 C 对象。

2005cpp-constructor-destructor

初始化顺序最好要按照变量在类声明的顺序一致,否则会出现下面的特殊情况:

#include<iostream>
 
using namespace std;
class Student1 {
    public:
        int a;
        int b;
        void fprint(){
            cout<<" a = "<<a<<" "<<"b = "<<b<<endl;
         }
         
         Student1(int i):b(i),a(b){ }    //异常顺序:发现a的值为0  b的值为2  说明初始化仅仅对b有效果,对a没有起到初始化作用 
//         Student1(int i):a(i),b(a){ } //正常顺序:发现a = b = 2 说明两个变量都是初始化了的  
         Student1()                         // 无参构造函数
        { 
            cout << "默认构造函数Student1" << endl ;
        }
    
        Student1(const Student1& t1) // 拷贝构造函数
        {
            cout << "拷贝构造函数Student1" << endl ;
            this->a = t1.a ;
        }
    
        Student1& operator = (const Student1& t1) // 赋值运算符
        {
            cout << "赋值函数Student1" << endl ;
            this->a = t1.a ;
            return *this;
        }
      
 };
class Student2
{
    public:
     
    Student1 test ;
    Student2(Student1 &t1){
        test  = t1 ;
    }
//     Student2(Student1 &t1):test(t1){}
};
int main()
{
    
    Student1 A(2);        //进入默认构造函数 
    Student2 B(A);        //进入拷贝构造函数 
    A.fprint();            //输出前面初始化的结果 
}

两种不同的初始化方法结果如下:

异常初始化顺序:

正常初始化顺序:

由上面的例子可知,初始化列表的顺序要跟你在类声明的顺序要一致。否则像上面的那种特殊情况,有些变量就不会被初始化。经过测试发现,类中变量为下面的情况也是能够正常初始化的:也就是说,只要成员变量的初始化不依赖其他成员变量,即使顺序不同也能正确的初始化。

int a;
int b;
int c;
Student1(int i):b(i),a(i),c(i){} 
main:
    Student1 A(2);    
    A.fprint();

结果:

int a;
int b;
int c;
Student1(int i,int j,int k):b(i),a(j),c(k){}
main:
    Student1 A(2,3,4);
    A.fprint();