再读C++ primer,提取重点

来源:互联网 发布:观军事知天下视频 编辑:程序博客网 时间:2024/05/16 11:23

C++初始化变量有好几种方法:

int a=0;

int a={0};

int a{0};

 都可以。 

如果要在多个文件中使用同一个变量,就必须将声明和定义分离。此时,变量的定义必须出现在且只能出现在一个文件中,而其他用到该变量的文件必须对其进行声明,却绝对不能重复定义。

如果想声明一个变量而非定义它,就在变量名前添加关键字extern,而且不要显式地初始化变量(任何包含了显式初始化的声明即成为定义。):

 

extern int i;   // 声明i而非定义

int j;          // 声明并定义

 

 

1引用被创建的同时必须被初始化(指针则可以在任何时候被初始化,最好也初始化,否则容易出错)。

 

2)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名b,可以这样写:

inta;

int&b=a;

int*p= &a; 这里&是取地址符。表示指针p指向(本语句等价于int *p; p= &a;)   

这就表明了ba引用,即a的别名。经过这样的声明,使用ab的作用相同,都代表同一变量 

int&b=a2;//企图使b变成a2的别名(引用)是不行的。这样是错误的。 

引用不是一个对象,所以不能定义引用的引用。

 

 

 constchar *p = "helloworld";    指针指向的变量不可以改变,指针本身可以改变(即指向常量的指针)

           这种情况下,以下语句是合法的:

                          char c = *p;

                          p++;

                               以下语句不合法:

                          *p = 'a';

                          p[5] = 'b';

    char*const p表示指针指向的变量可以改变,但指针本身不能改变。

                      比如        int i=4;

                                 int *const p = &i;

                              那么*p =5;是合法的,p++是不合法的

    constchar  *const p两者都不可以改变。

理解这些声明的技巧在于,查看关键字const右边来确定什么被声明为常量 ,如果该const的右边是类型,则值是常量(底层const);如果const的右边是指针变量,则指针本身是常量(顶层const)。赋值拷贝的时候要特别注意底层const,一般来说,非常量可以赋值给常量,反之则不行。(只是赋值哦,没说改变Const)

例如constint *a = int *b;  反之不行。

 

C++11中引入的auto主要有两种用途:自动类型推断和返回值占位。autoC++98中的标识临时变量的语义,由于使用极少且多余,在C++11中已被删除。

auto自动类型推断,用于从初始化表达式中推断出变量的数据类型。通过auto的自动类型推断,可以大大简化我们的编程工作。下面是一些使用auto的例子。

 

auto a; // 错误,没有初始化表达式,无法推断出a的类型  

auto int 10 // 错误,auto临时变量的语义在C++ 11中已不存在  

auto 10 

auto 'A' 

auto s("hello"); 

vector<</span>int> vctTemp; 

auto it vctTemp.begin(); 

auto ptr [](){ cout << "hello world" << endl; }; 

 

另外,在使用模板技术时,如果某个变量的类型依赖于模板参数,不使用auto将很难确定变量的类型(使用auto后,将由编译器自动进行确定)。下面是一个具体的例子。

 

template <</span>class T, class U> 

void Multiply(T t, u) 

auto t*u; 

auto返回值占位,主要与decltype配合使用,用于返回值类型后置时的占位。

 

template <</span>class T, class U> 

auto Multiply(T t, u)->decltype(t*u) 

typedef decltype(t*u) NewType; 

NewType *pResult new NewType(t*u); 

return *pResult; 

至于为什么需要将返回值类型后置,这里简单说明一下。如果没有后置,则函数声明为decltype(t*u)Multiply(T t, Uu),但此时模板参数tu还未声明,编译无法通过。

如果这个表达式是个函数,decltype给出的类型为函数返回值的类型。

 

[cpp] viewplaincopy

int add(int i, int j){ return i+j;  

decltype(add(5,6)) var 5;//Here the type of var is return of add() -> which is int  

非常重要的标记一下,decltype不会执行表达式而auto会,他仅仅推论一下表达式的类型。

 


int foo(){}  

decltype( foo() x; // is an int and note that   

                     // foo() is not actually called at runtime  

 


 头文件不应包含using声明


strings1="hello", s2="world";

strings3=s1+","+s2;

string s4=s1+",";

string s4=s1+s2;

而+两侧运算对象至少一个是string类型:

string s6="hello"+","+s2; //错误, 不能把字面值直接相加


基于范围的for 语句

遍历给定序列中每个元素并操作

简单例子:把string对象中的字符每行一个个的输出出来


string str("some strings");

for (auto c : str)

cout<<c<<endl;

这里使用auto让编译器来决定c变量的类型,这里是char型 


但如果想改变string对象中的字符值,循环变量必须定义成引用

比如将string对象字符全变为大写

stringstr("some strings");

for (auto&c : str)

c=toupper(c);

cout<<s<<endl;

这里toupper函数是定义在〈cctype〉头文件中的处理string中的某个特定字符的集合。



标准模板库类型vector表对象的集合。一般用{ }初始化,不容易错。

vector 〈string〉 v1{"a","ab","abc"};

除了以下情况用()初始化

vector 〈string〉 svec(10,"hi!");   表示10个string类型的元素,每个都被初始化为"hi!"。

vector 〈int〉svec(10);  表示10个int类型的元素,每个都被初始化为0。

vector 〈int〉svec(10,1);  表示10个int类型的元素,每个都被初始化为1。

如果这时使用{}

vector 〈int〉 svec{10};

表示1个int类型元素,该元素是10。

vector 〈int〉svec{10,1};  表示2个int类型元素,分别是10和1。 

它的成员函数push_back()

vector 〈int〉v2;

for(inti=0; i!=100; ++i)

v2.push_back(i); //依次将整数值放到v2的尾端。。使用C++,在for循环中要习惯使用!=而不是


 

迭代器

(1)每种容器类型都定义了自己的迭代器类型,如vector:
vector::iteratoriter;这条语句定义了一个名为iter的变量,它的数据类型是由vector定义的iterator类型。
(2)使用迭代器读取vector中的每一个元素:
vector 《int》ivec(10,1);
for(vector::iteratoriter=ivec.begin();iter!=ivec.end();++iter)
{
*iter=2;//使用 * 访问迭代器所指向的元素
}
其中begin()成员函数指向容器中的第一个元素,而end()成员函数其实指向最后一个元素的下一个元素,所以没什么真正含义,只是一个标记而已,如果v.begin()=v.end(),则容器为空。
const_iterator:
只能读取容器中的元素,而不能修改。
for(autoit=ivec.cbegin();it!=ivec.cend();it++)
{
cout<<*it;
}
这里cbegin()和cend()是常量迭代器,是const_interator的类型,只能用于读取容器中的元素,不能修改容器中的元素。
注意,但凡是使用了迭代器的循环体,都不要想迭代器所属的容器中添加元素。

解引用操作符:所有迭代器都提供了解引用操作符(*),用于获取迭代器所指向的元素。以下代码都是合法的。

std::cout << *iter; *iter = 5; *iter = *iter + 5;

取后继元素操作符:所有迭代器都可以通过iter++、++iter 操作符获取其后继元素的迭代器。

auto pbeg=v.begin();

while(pbeg!=v.end() && *pbeg>=0) //输出元素直到第一个负值

cout<<*pbeg++<<endl;     等价于*(pbeg++) , 解引用 返回的是pbeg未改变之前的值,然后再将指针pbeg+1。这是一种广泛的用法。

不过注意,C++中一般更倾向于使用前置++, 因为这样先改变指针再返回指针改变后所指向的值,节省空间并且更易理解。


迭代器运算
it+n  表示迭代器所指示的位置相比原来的向右(前)移动了n个元素,
it-n 表示迭代器所指示的位置相比原来的向左(后)移动了n个元素,


数组的维度必须是一个常量表达式:
int a[10];
int  a[var]; //除非var是constexpr,否则错误。
在C++中,数组大小固定,,对某些程序性能较好,但灵活性较差,如果不清楚元素的确切个护士,请使用vector.

不能把一个数组直接赋值给另一个数组。如  intA[]={1,1,1};  intB[]; 不能 B=A;所以数组作为函数形参传递的时候也要特别注意。

也不允许用vector对象来初始化数组。但允许使用数组来初始化vector 对象。
不存在引用的数组。
数组和指针关系密切:
string nums[] ={"one","two","three"};     //这里没声明数组的大小,但编译器会根据初始值的数量推算出为3
string *p=&nums[0];

不存在引用的数组,但存在数组的引用
int &ref[10]; 错
int (&ref)[10];对 包含10个Int元素的数组ref的引用

多维数组的初始化
允许使用花括号括起来的一组值初始化多维数组,这点和普通的数组一样。下面的初始化形式中,多维数组的每一行分别用花括号括了起来:

int ia[3][4] ={       // 三个元素,每个元素都是大小为4的数组

    {0, 1, 2,3},      // 第1行的初始值

    {4, 5, 6,7},       // 第2行的初始值

    {8, 9, 10,11}      // 第3行的初始值

};

其中内层嵌套着的花括号并非必需的,例如下面的初始化语句,形式上更为简洁,完成的功能和上面这段代码完全一样:

// 没有标识每行的花括号,与之前的初始化语句是等价的

int ia[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};


程序中经常会用到两层嵌套的for循环来处理多维数组的元素:

constexpr size_t rowCnt = 3, colCnt = 4;

int ia[rowCnt][colCnt]; // 12 个未初始化的元素

// 对于每一行

for (size_t i = 0; i != rowCnt; ++i) {

   //对于行内的每一列

    for (size_tj = 0; j != colCnt; ++j) {

       // 将元素的位置索引作为它的值

       ia[i][j] = i * colCnt + j;

    }

}

外层的for循环遍历ia的所有元素,注意这里的元素是一维数组;内层的for循环则遍历那些一维数组的整数元素。此例中,我们将元素的值设为该元素在整个数组中的序号。

使用范围 for语句处理多维数组
由于在C++11新标准中新增了范围for语句,所以前一个程序可以简化为如下形式:

size_t cnt = 0;

for (auto &row :ia)           // 对于外层数组的每一个元素

    for (auto&col : row){    // 对于内层数组的每一个元素

       col =cnt;             // 将下一个值赋给该元素

       ++cnt;                 // 将 cnt加1

}

因为要改变元素的值,所以得把控制变量row和col声明成引用类型。

标准库函数 begin和end 跟容器的bengin和end成员函数功能差不多。不过毕竟数组不是类类型,所以不太一样。

int ia[] = {0,1,2,3,4,5,6,7};

int *beg = begin(ia); //指向ia的首元素

int *last = end(ia);  //指向数组ia的最后一个元素的下一个位置。 


位与,位或,位异或

7&8 = 0000 0111 & 0000 1000 = 0000 0000 = 0


7|8 = 0000 0111 | 0000 1000 = 0000 1111 = 15


7^8 = 0000 0111 ^ 0000 1000 = 0000 0111 = 7

  

switch(ch){

case 'a':  ... ; break;

case 3:   .... ;  break;

}

case 后面只能是整型常量表达式(整数,字母等),不能是小数或者变量。



try
{
//程序中抛出异常
throw value;
}


catch(valuetype v)
{
//例外处理程序段
}


语法小结:throw抛出值,catch接受,当然,throw必须在“try语句块”中才有效。

int main() 

try 

cout << "在 try block 中, 准备抛出一个int数据类型的异常." << endl; 
throw 1; 

//程序如果执行到下面这句表示还没有碰到 int 的异常。
cout << "在 try block 中, 准备抛出一个double数据类型的异常." << endl; 
throw 0.5; 


catch( int& value 

cout << "在 catch block 中, int数据类型处理异常错误。”<< endl; 


catch( double& d_value 

cout << "在 catch block 中, double数据类型处理异常错误。”<< endl; 


return 0; 


http://blog.sina.com.cn/s/blog_a9303fd901018ost.html


建议函数和变量在头文件中声明,在源文件中定义。

对于不需要修改的变量,使用常量引用来传递参数可以既保证安全性又保证效率!


调用一个返回引用的函数得到左值

函数返回引用类型时,没有复制返回值。相反,返回的是对象本身。

返回指向函数调用前就已经存在的对象的引用是正确的。

我们能为返回类型是非常量引用的函数的结果赋值:

 char &get_val (string &str,string::size_type ix)

{

return str[ix];         //返回某一位

}


int main()

{

string s("a value"); //初始化

get_val(s,0)='A';

cout<<s<<endl;

}


const_cast   

去掉const属性:const_cast《int *》 (&num),常用,因为不能把一个const变量直接赋给一个非const变量,必须要转换。

把const的num地址转化为普通int地址  

const int ica = 100;   

   int ia = const_cast《int &》(ica);  

 

   cout<< ia<<ica<<endl; 

都是100

使用范围:

1. 常量指针被转化成非常量指针,转换后指针指向原来的变量

  1. const int ica 100;    
  2.     int ia const_cast<</span>int *>(&ica);    
  3.     *ia 200;    
  4.     cout<< *ia <<ica<<endl;   //200 100    

2.常量引用转为非常量引用。
3. 常量对象(或基本类型)不可以被转换成非常量对象(或基本类型)。
  1.  const int 100;  
  2.  int const_cast<</span>int>(i);  //不允许

默认实参是通过给形参表中的形参提供明确的初始值来指定的。程序员可为一个或 多个形参定义默认值。但是,如果有一个形参具有默认实参,那么,它后面所有的 形参都必须有默认实参。

既可以在函数声明中也可以在函数定义中指定默认实参,但是在一个文件中,只能为一个形参指定默认实参一次;

个人比较推荐这种格式: 在申明中将默认参数给出,在定义中不再给出!

  1. //CPerson.h  
  2. int initPerson(int old, string name "二毛"string race "汉");  
  3. //CPerson.cpp  
  4. #include "CPerson.h"  
  5. //int initPerson(int old, string name "二毛", string race "汉"){...}  
  6.   
  7. int initPerson(int old, string name, string race)      //定义
  8. {...}  


构造函数用来初始化类的对象。只要类的对象被创建,就会执行构造函数。构造函数的名字和类名相同。构造函数没有返回类型。

构造函数初始化列表以一个冒号开始,接着是以逗号分隔的数据成员列表,每个数据成员后面跟一个放在括号中的初始化式。例如:

 

class CExample {
public:
    
int a;
    
float b;
    
//构造函数初始化列表
    CExample(): a(0),b(8.8)
    
{}
    
//构造函数内部赋值
    CExample()
    
{
        a
=0;
        b
=8.8;
    }

}
;

上面的例子中两个构造函数的结果是一样的。

也可以在类的外部定义构造函数

 CExample:: CExample()

  {
       a
=0;
       b
=8.8;
    }

如果没有为一个类显式地定义任何构造函数,编译器将自动为这个类生成默认构造函数。

1.什么是默认构造函数?

一个函数是默认构造函数当且仅当调用它可以不需要传入任何参数。这个函数可以是用户自定义的,也可以是编译器产生的。

2.编译器什么时候隐式声明默认构造函数?

有两个条件:

 

  • 该类没有显式声明任何构造函数。--既然你都定义了,系统就不给你生成了。
  • 1、每个类必须有一个构造函数,否则没法创建对象;
  • 2、若programer没有提供任何构造函数,则C++提供一个默认的构造函数,该默认构造函数是无参构造函数,它仅负责创建对象,不做任何初始化的工作;
    3、只要programer定义了一个构造函数(不管是无参还是有参构造),C++就不再提供默认的默认构造函数。即如果为类定义了一个带参的构造函数,还想要无参构造函数,就必须自己定义;
    4、与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0,否则,对象值是随机的。

 定义类时, 使用class和struct关键字的区别:

默认情况下,struct的保护级别为public,而class的保护级别为private。

友元 :优点:提高了程序的运行效率。

缺点:破坏了类的封装性和数据的透明性。


友元函数  
      友元函数是可以直接访问类的私有成员的非成员函数。它是定义在类外的普通函数,它不属于任何类,但需要在类的定义中加以声明,声明时只需在友元的名称前加上关键字friend,其格式如下:
      friend 类型 函数名(形式参数);

class Point{ 

public:Point(double xx, double yy) 

{ x=xx;y=yy; } 

void Getxy();

friend double Distance(Point &a, Point &b); 

private: double x, y; 

};

void Point::Getxy() {

cout<<"("<<<","<<Y<<")"<<endl;} 

doubleDistance(Point &a, Point &b)

{

double dx =a.x -b.x; double dy = a.y - b.y; returnsqrt(dx*dx+dy*dy); 

}

void main(){ Point p1(3.0, 4.0), p2(6.0, 8.0);

p1.Getxy();p2.Getxy();

double d =Distance(p1, p2); 

cout<<"Distanceis"<< endl; }

注意友元函数Distance的调用,像普通成员那样调用,因为它不属于类。所以不像p1.Getxy();前面要带对象.

友元类  
      友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。       
      当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:
      friendclass 类名;
      其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。

      例如,以下语句说明类B是类A的友元类:
      classA
      {
             
      public:
             friendclass B;
             
      };
      经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。

      使用友元类时注意:
            (1) 
友元关系不能被继承。 
            (2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
            (3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明 

在类中,静态成员可以实现多个对象之间的数据共享

 一、静态数据成员
   在类内对成员的声明之前加上关键字static,将数据成员声明为static,即为静态数据成员。
    注意:*静态数据成员不属于某一个对象,它是属于类的,其值为所有本类的对象所共享。
         * 静态数据成员是在所有对象之外单独开辟内存空间的。只要在类中声明了静态数据成员,
           即使没有定义对象,系统也为其分配内存空间。
         * 静态数据成员必须在类体外进行初始化,格式如下:
           数据类型  类名::静态数据成员名=初值

初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆。

初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员

* 静态成员函数与普通成员函数的根本区别是,静态成员函数没有this指针。因而静态成员函数可直接访问本类中的静态数据成员,对于非静态数据成员,一定要加上“对象名.”。

非静态成员函数可以任意地访问静态成员函数和静态数据成员。

静态成员函数不能直接访问非静态成员函数和非静态数据成员,需要通过对象调用

#include

using namespacestd;

classStudent

{

  public:

     Student(int n=1000,floats=0):num(n),score(s){}  //构造函数

     void printinfo();  //普通成员函数

     staticvoid printaver(Student*); //静态成员函数,形参为Student类的指针

  private:

     int num;

     float score;

     staticfloat total;  //静态数据成员

};

float Student::total=0;  //静态数据成员初始化


voidStudent::printinfo()  //定义成员函数,输出相关信息

{

   cout<<num<<" "<<score<<endl;

}

void Student::printaver(Student* ps) //定义静态成员函数,输出总分数及平均分

{

   int n;

   cout<<"how many students:";

   cin>>n;

   for(int i=0;i

   {

     total=total+(ps+i)->score; 引用静态数据成员及非静态数据成员的方法不同

   }

   cout<<"  totalis:"<<total<<endl;

   cout<<"averageis:"<<total/n<<endl;;

}


intmain()

{

  Student stu[]={Student(1000,96),  //定义对象数组

               Student(1001,92),

               Student(1002,65)};

  for(int i=0;i<3;i++)   //循环调用printinfo()函数,输出每个学生信息

     {

      cout<<i+1<<".";

      stu[i].printinfo();

     }

  Student::printaver(stu); //调用静态成员函数,实参为对象数组的起始地址

  return 0;

 

This 指针:

当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。

this指针只能在一个类的成员函数中调用,它表示当前对象的地址。下面是一个例子:
   void Date::setMonth( int mn )     {      month = mn; // 这三句是等价的      this->month = mn;      (*this).month = mn;     } 1. this只能在成员函数中使用。全局函数,静态函数都不能使用this。

this在成员函数的开始执行前构造,在成员的执行结束后清除。

我们只有获得一个对象后,才能通过对象使用this指针

在以下场景中,经常需要显式引用this指针
   (1)为实现对象的链式引用(如例1); 
   (2)为避免对同一对象进行赋值操作(如例2);
   (3) 在实现一些数据结构时,如list.

7. 举例:

//例1:

 

#include
#include

class Person {
     public:
         typedef enum {
                BOY = 0,
                GIRL = !BOY
         } SexType;
     public:
            Person(char *n, int a, SexType s)
            {
                name = new char[strlen(n)+1];  //这里的 name等价于this->name
                strcpy(name,n);               //这里的 name 等价于this->name
                age =a;                      //这里的 age 等价于this->age
                sex =s;                      //这里的 sex 等价于this->sex
            }
          

           intget_age(void) const
            
                return age;    //这里的age 等价于this->age
            }


            Person& add_age(int a)

            {
                age +=a;  //这里的age 等价于this->age
                return *this; // 返回本对象的引用 
            }


     private:

           char *name;
           int age;
           SexType sex;
};

void TestPerson(void)
{
    Person ZhangSan("ZhangSan", 20, Person::BOY);
    printf("ZhangSan.age = %d\n", ZhangSan.get_age());
    printf("ZhangSan.add_age = %d\n", ZhangSan.add_age(1).get_age());//增加1岁的同时,可以对新的年龄直接输出;

    return;
        

int main(void)
{
   TestPerson();
   while(1);
    
}

 

//例2:

 

#include

class Location {
    int X,Y;//默认为私有的
 public:
    void init(int x,int y) { X =x; Y = y;};
    void assign(Location& pointer);
    int GetX(){ return X; }
    int GetY(){ return Y; }
};

void Location::assign(Location& pointer)
{
   if(&pointer!=this) 

//同一对象之间的赋值没有意义,所以要保证pointer不等于this, this是当前对象的地址,pointer是当前类对象中调用的

//另一个对象的地址参数

    {
       X=pointer.X;
       Y=pointer.Y;
    }
}

int main()
{
    Location x;
    x.init(5,4);
     
    Location y;
    y.assign(x);
     
    printf("x.X = %d, x.Y = %d \n",  x.GetX(),x.GetY());
    printf("y.X = %d, y.Y = %d ",  y.GetX(),y.GetY());
     
    while(1);
    return 0;
}


动态数组

c++要求定义数组时,必须明确给定数组的大小,要不然编译通不过 
 
  如: int Array[5];正确

      int i=5;
      int Array[i]; 错误 因为在编译阶段,编译器并不知道 i 的值是多少

  那么,我们该如何解决定义长度未知的数组呢?
   答案是:new动态定义数组 

   因为new就是用来动态开辟空间的,所以当然可以用来开辟一个数组空间
   
   这样,下面的语句:
    intsize=50;
    int *p=newint[size]; 是正确的

最后 ,因为调用了new, 千万千万别忘记在用完之后,将其所占资源 delete 掉.delete动态数组的时候,在指针前加[]

delete []  p ;


拷贝构造函数:

下面  网址中说得很清楚

http://blog.csdn.net/lwbeyond/article/details/6202256

我摘抄一段过来

一.什么是拷贝构造函数

首先对于普通类型的对象来说,它们之间的复制是很简单的,例如:

  [c-sharp] view plaincopy

  1. int 100;  
  2. int a;   


而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
下面看一个类对象拷贝的简单例子。

用已有对象初始化新建对象

  [c-sharp] view plaincopy

  1. #include   
  2. using namespace std;  
  3.   
  4. class CExample  
  5. private 
  6.     int a;  
  7. public 
  8.     //构造函数  
  9.     CExample(int b)  
  10.     b;}  
  11.       
  12.     //拷贝构造函数  
  13.     CExample(const CExample& C)  
  14.      
  15.         C.a;  
  16.      
  17.   
  18.     //一般函数  
  19.     void Show ()  
  20.      
  21.         cout<<a<<endl;  
  22.      
  23. };  
  24.   
  25. int main()  
  26.  
  27.     CExample A(100);  
  28.     CExample A; // CExample B(A); 也是一样的  
  29.      B.Show ();  
  30.     return 0;  
  31. }   

运行程序,屏幕输出100。从以上代码的运行结果可以看出,系统为对象B 分配了内存并完成了与对象 A的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的

CExample(constCExample& C) 就是我们自定义的拷贝构造函数。可见,拷贝构造函数是一种特殊的构造函数,函数的名称必须和类名称一致,它必须的一个参数是本类型的一个引用变量

函数调用时参数是对象

  harp] view plaincopy

  1. class CExample   
  2.  
  3. private 
  4.  int a;  
  5.   
  6. public 
  7.  //构造函数  
  8.  CExample(int b)  
  9.    
  10.   b;  
  11.   cout<<"creat: "<<a<<endl;  
  12.   
  13.   
  14.  //拷贝构造  
  15.  CExample(const CExample& C)  
  16.   
  17.   C.a;  
  18.   cout<<"copy"<<endl;  
  19.   
  20.    
  21.  //析构函数  
  22.  ~CExample()  
  23.   
  24.   cout<< "delete: "<<a<<endl;  
  25.   
  26.   
  27.      void Show ()  
  28.   
  29.          cout<<a<<endl;  
  30.       
  31. };  
  32.   
  33. //全局函数,传入的是对象  
  34. void g_Fun(CExample C)  
  35.  
  36.  cout<<"test"<<endl;  
  37.  
  38.   
  39. int main()  
  40.  
  41.  CExample test(1);  
  42.  //传入对象  
  43.  g_Fun(test);  
  44.   
  45.  return 0;  
  46.  


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

 拷贝构造函数和拷贝赋值运算符区别

a; 
 b=a;  //拷贝构造函数调用  
 //或  
 b(a);  //拷贝构造函数调用  
 ///////////////////////////////////     
 a;  
 b;  
 b=a;   //赋值运算符调用  

 

你只需要记住,在C++语言里,  
 String  s2(s1);  
 String  s3   s1;  
 只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。
0 0