读书笔记——类的学习心得(一)

 c++里类的概念的目标就是为程序员提供一种建立新类型的工具,使这种性的是用
能够像内部类型一样方便。
类组织,主要有两种方式:派生类和模板。
派生类:通过类层次结构创建的类。
模板:类模板创建的类。
定义新类型的基本思想:就是将视线中那些并非必然的细节,与那些对这个类型的正确使用至关重要的性质区

分开来。即外部的界面并不关心类内部的实现。
一个类就是一个用户定义类型。可以把内存中的东西都看成一个活物,一个类就是一个个体。
围绕着一个类需要做三步工作:定义类、创建类的对象、类的基本功能。这三块内容有函数连接。
其中函数又以其功能可分为:成员函数、构造函数、析构函数。
成员函数
一个类里声明的函数被称作成员函数,这种变量只能通过适当类型的特定变量,采用标准的结构成员访问语法

形式调用。这里要说明struct也是一种类。在成员函数里面,结构内部的各个成员的名字都可以直接使用,不

必显式的引用某个对象。类的成员函数总知道他是为那个对象而调用的。保证这一点是因为要求在定义成员函

数时就必须给出有关结构的名字。
结构
class X{……};被称为一个类定义,类声明也被说成类定义。类定义可以由于#include的使用而在不同的源文

件里重复出现。
访问控制
通过一个实例。
class Data{
      int d,m,y;
public:
      void init(int dd,int mm,int yy);    //初始化
      void add_year(int yy);         //加n年
      void add_month(int mm);       //加n月
      void add_doy(int dd);         //加n天
};

标号public将这个类得体分为两个部分,其中的第一部分(私用部分)只能由成员函数使用;第二部分(公用

部分)则构成了该类的对象的公用界面。
严格限制到明确说明的一组函数——成员函数,将会使排错工作在程序运行之前就可以完成。可以认为类型

Data的改变是由于成员函数的改变造成的。如果一个类的数据成员改变了,只需要修改成员函数就可以使用新

的成员数据了。但是需要重新编译,因为随着成员数据的改变内部变量的大小也可能随之改变了。
对于私用成员的保护依靠的是对于使用类成员名字的限制。该限制的完成是由编译器来保证,有符号表记下私

有成员的名字。
构造函数
构造函数将构造器一个给定类型的值,其目的就是去完成对象的初始化。
构造函数具有与类同样的名字。
有的时候需要多个构造函数对类进行初始化。
但是要权衡,只给出必要多的构造函数。
初始化方式有三种:通过成员函数初始化;程序员调用函数初始化;通过类对象的复制初始化。
程序员自己调用函数时可能存在两个问题:忘记调用或调用两次。成员函数有系统调用会避免这个问题。对类对

象的复制涉及到深复制和浅复制问题。
静态成员
如果一个变量是累的一部分,但却不是该类的各个对象的一部分,它就被称为是一个static静态成员。
一个static成员只有唯一的一份副本,而不想常规的非static成员那样在每个对象里各有一份副本。
一个需要访问类成员,然而却并不需要针对特定对象去调用的函数,也被称为一个static成员函数。
const成员函数不能声明为static。因为:
class A
{
void fun()
};

被编译器翻译成了
struct A
{
void fun(A const* this){}
};


class A
{
void fun() const -翻译成了: void (const A const *this){}
};


struct A
{
static void fun()
}

只相当于:
namespace A
{
static void fun()
}

因此 const本应该用来修饰this指针,但static函数没有,所以编译器无从下手。静态成员没有this
指针所以不能用const来修饰。
静态成员也可以像任何其他成员一样引用。对于静态成员的引用不必提到任何对象,相反,应该给成员的名字

加上作为限定词的类名字。
静态成员——包括函数和数据成员——都必须在某个地方另行定义。
类对象的复制
对于普通类型的对象来说,他们之间的复制是很简单的,例如:
int a=10;
int b=a;
自己定义的类的对象同样是对象,可以用同一个类的对象复制对该类的其他对象进行初始化。
即使声明了构造函数,也还是可以这样初始化。按照默认约定,类对象的复制就是其中的各个成员的复制。
还可以定义一个复制构造函数,由他提供所需要的行为。
还可以通过赋值进行按默认方式的复制。用户也可以定义合适的赋值运算符。默认的语义就是按成员复制。
以下内容摘自网络。
***********************************************************************************************
谁也不能阻止我们用以下的方式进行复制,例如:
#include <iostream>
using namespace  std;

class Test
{
  public:
  Test(int temp)
  {
   p1=temp;
  }
  protected:
   int p1;
};

void main()
{
  Test a(99);
  Test b=a;
}

普通对象和类对象同为对象,他们之间的特性有相似之处也有不同之处,类对象内部存在成员变量,而普通对

象是没有的,当同样的复制方法发生在不同的对象上的时候,那么系统对他们进行的操作也是不一样的,就类

对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的,在上面的代码中,我们并没有看到拷

贝构造函数,同样完成了复制工作,这又是为什么呢?因为当一个类没有自定义的拷贝构造函数的时候系统会

自动提供一个默认的拷贝构造函数,来完成复制工作。
下面,我们为了说明情况,就普通情况而言(以上面的代码为例),我们来自己定义一个与系统默认拷贝构造函

数一样的拷贝构造函数,看看它的内部是如何工作的!
代码如下:
 #include <iostream>
using namespace std;

class Test
{
  public:
   Test(int temp)
   {
          p1=temp;
   }
      Test (Test &c_t) //这里就是自定义的拷贝构造函数
   {
    cout<<"进入copy构造函数"<<endl;
     p1=c_t.p1;//这句如果去掉就不能完成
               //复制工作了,此句复制过程的核心语句

   }

  public:
 int p1;
};

void main()
{
    Test a(99);
    Test b=a;
    cout<<cin.get();
}

上面代码中的Test(Test &c_t)就是我们自定义的拷贝构造函数,拷贝构造函数的名称必须与类名称一致,函数

的形式参数是本类型的一个引用变量,且必须是引用。
当用一个已经初始化过了的自定义类类型对象去初始化另一个新构造的对象的时候,拷贝构造函数就会被自动

调用,如果你没有自定义拷贝构造函数的时候系统将会提供给一个默认的拷贝构造函数来完成这个过程,上面

代码的复制核心语句就是通过Test(Test &c_t)拷贝构造函数内的p1=c_t.p1;语句完成的。如果取掉这句代码,

那么b对象的p1属性将得到一个未知的随机值。
深拷贝与浅拷贝
就上面的代码情况而言,很多人会问到,既然系统会自动提供一个默认的拷贝构造函数来处理复制,那么我们

没有意义要去自定义拷贝构造函数呀,对,就普通情况而言这的确是没有必要的,但在某写状况下,类体内的

成员是需要开辟动态开辟堆内存的,如果我们不自定义拷贝构造函数而让系统自己处理,那么就会导致堆内存的

所属权产生混乱,试想一下,已经开辟的一段堆地址原来是属于对象a的,由于复制过程的发生,b对象取得了a

对象已经开辟的堆地址,一旦程序产生析构,释放内存的时候,系统将不清楚内存地址到底是属于谁的,连续

发生两次析构就会产生运行错误。
为了更详细的说明问题,请看如下的代码。
#include <iostream>
using namespace std;

class Internet
{
public:
 Internet(char*name,char*address)
 {
  cout<<"载入构造函数"<<strcpy(Internet::name,name);
  strcpy(Internet::address,address);
  cname=new char[strlen(name)+1];
  if(cname!=NULL)
  {
   strcpy(Internet::cname,name);
  }
 }
 Internet(Internet &temp)
 {
  cout<<"载入COPY构造函数"<<strcpy(Internet::name,temp.name);
  strcpy(Internet::address,temp.address);
  cname=new char[strlen(name)+1];//这里注意,深拷贝的体现!
  if(cname!=NULL)
  {
   strcpy(Internet::cname,name);
  }
 }
 ~Internet()
 {
  cout<<"载入析构函数!";
  delete[] cname;
  cin.get();
 }
 void show();
protected:
 char name[20];
 char address[30];
 char*cname;
};
void Internet::show()
{
 cout<<'/n';
}
void test(Internet ts)
{
 cout<<"载入test函数"<<'/n';
}
void main()
{
 Internet a("中国软件开发实验室","www。cndev-lab。com");
 Internet b =a;
 b.show();
 test(b);
}
上面代码就演示了深拷贝的问题,对对象b的cname属性采取了新开辟内存的方式避免了内存归属不清所导致析构

释放空间时候的错误,最后我必须提一下,对于上面的程序我的解释并不多,就是希望读者本身运行程序观察变

化,进而深刻理解。

深拷贝和浅拷贝的定义可以简单理解成:如果一个类拥有资源(堆,或者是其它系统资源),当这个类的对象发

生复制过程的时候,这个过程就可以叫做深拷贝,反之对象存在资源但复制过程并未复制资源的情况视为浅拷

贝。
浅拷贝资源后在释放资源的时候会产生资源归属不清的情况导致程序运行出错,这点尤其需要注意!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值