学习笔记 C++ 类与对象

类与对象

学了之后 感觉类与结构体其实差不太多

1.声明类

class Human
{
	string Name;//内部变量
	int Age,Birth;
	
	void Talk(string Text);//封装函数
	void IntroduceSelf();
};

注:属于类的函数被称为方法

2.实例化对象

就像结构体一样 运行程序时使用类的话也需要实例化

class Human
{
    //...
};

Human person;

3.访问类的成员

访问类的成员也类似于结构体 存在句点访问和指针运算符(->)访问

class Human
{
    public:
      string Name;
      int Age,Birth;
	
      void Talk(string Text);
      void IntroduceSelf();
};
Human person;
Human* people;

int main()
{
    cout<<person.Age<<endl;
    people->Talk("string");
    return 0;
}

4.关键字private和public

定义类的时候有的时候会这么定义

class Human
{
    private:
        string Name;//内部变量
        int Age,Birth;
    public:
        void Talk(string Text);//封装函数
        void IntroduceSelf();
};

诚如字面意思

private下面定义的变量和函数只能在类内部调用

public下面定义的变量和函数可以在外部调用

就像这样

学习笔记 C++ 类与对象

这样的优点是:可以通过合适的方法暴露内部变量 从而防止外部输入对内部设置造成破坏

这也体现了C++作为面向对象的程序设计语言的特点:C++让类的设计者能够控制类属性的访问与操作方式

5.关键字private实现数据抽象

有的时候类内部的数据不能直接公开

所以 类内部使用private定义数据 加以抽象化输出 实际上就是外部无法直接访问 合法的访问又经过了内部加工

class Human
{
    private:
      string Name;//内部变量
      int Age,Birth;
    public:
      int GetAge()
      {
	if(Age>30) cout<<Age-2<<endl;//唯一的合法访问经过了内部加工
	else cout<<Age<<endl;
      }  
};

6.构造函数

构造函数是一种特殊的函数 ta与类同名且不返回任何值

内部声明如下:

class Human
{
    public:
      Human()
      {
          //... 
      }
};

外部声明如下:

class Human
{
    public:
      Human();
};
Human::Human()
{

}

构造函数总是在创建对象的时候调用 是类成员变量初始化为已知值的理想场所

比如说:

class Human
{
    private:
      string Name;
      int Age;
    public:
      Human()
      {
        Name="WangXiaoMing";
      	Age=20;
      }
};

同样 构造函数支持重载 可创建一个默认构造函数和多个包含不同形参的重载构造函数

#include<bits/stdc++.h>
using namespace std;
class Human
{
    private:
      string Name;
      int Age;
    public:
      Human()
      {
      	Name="WangXiaoMing";
      	Age=20;
      }
      Human(string InputName)
      {
	Name=InputName;
	Age=38;
      }
	Human(string InputName,int InputAge)
      {
	Name=InputName;
        Age=InputAge;
      }
      void IntroduceSelf()
      {
        cout<<Name<<" "<<Age<<endl;
      }
};

int main()
{
  Human cdy;
  Human wzy("GaoXiaoHong");
  Human zjz("YuXiaoLiang",17);
  cdy.IntroduceSelf();wzy.IntroduceSelf();zjz.IntroduceSelf();
  return 0;
}

//
Output:
WangXiaoMing 20
GaoXiaoHong 38
YuXiaoLiang 17
//

注意 如果一个函数只存在重载的构造函数而不存在默认的构造函数的话

如果我们还直接Human cdy;这样直接声明类的话是不合法的

必须要提供内部变量的默认值

可以在声明的时候提供 Human zjz("YuXiaoLiang",17);

也可以在定义构造函数的时候添加默认值

class Human
{
    private:
      string Name;
      int Age;
    public:
      Human(string InputName="Adam",int InputAge=20)//添加默认值
      {
	Name=InputName;
        Age=InputAge;
      }
      void IntroduceSelf()
      {
        cout<<Name<<" "<<Age<<endl;
      }
};

Human cdy;
Human zjz("YuXiaoLiang",17);
//这样声明都是合法的

我们籍此也能明白 默认构造函数是调用时可不提供参数的构造函数,不意味ta不接受任何参数

同时还存在一种写法就是初始化列表

class Human
{
    private:
      string Name;
      int Age;
    public:
      Human(string InputName="Adam",int InputAge=20):Name(InputName),Age(InputAge){}
      void IntroduceSelf()
      {
        cout<<Name<<" "<<Age<<endl;
      }
};

Human cdy;
Human zjz("YuXiaoLiang",17);
//这样声明都是合法的

7.析构函数

与构造函数一样 析构函数也是一个看起来与类同名的函数 但是前面有一个波浪号~

class Human
{
  ~Human();
};

析构函数同构造函数类似 既可以在类内部声明 也可以在类外部声明

析构函数的作用与构造函数完全相反

每当对象不再在作用域内或者通过delete删除继而被销毁时 都将调用析构函数

析构函数是重置变量以及释放动态内存和其他资源的理想场所

我们使用C风格char* s;的字符串时 必须自己管理内存分配 因此建议使用std::string等工具

后者都是类 而且充分利用了构造函数和析构函数

接下来演示一下如何在构造函数中为一个字符串分配内存并在析构函数中释放

#include<bits/stdc++.h>
using namespace std;
class MyString
{
  private:
    char* buffer;
  public:
    MyString(const char* InitialString)
    {
      if(InitialString!=NULL)
      {
        buffer=new char[strlen(InitialString)+1];
        strcpy(buffer,InitialString);	
      }
      else buffer=NULL;	
    }	
    ~MyString()
    {//析构函数
      printf("Now we delete.\n");	
      if(buffer!=NULL) delete[] buffer;	
    }
    int getlen(){return strlen(buffer);}
    const char* getstring(){return buffer;}
};
int main()
{
  MyString person("Say Hello To The World");
  printf("It has %d characters.\n",person.getlen());
  printf("buffer contains:%s.\n",person.getstring());
  return 0;
} 
//
Output:
It has 22 characters.
buffer contains:Say Hello To The World.
Now we delete.
//

8.复制构造函数

我们调用函数的时候double Area(double InputRadius)实参会被复制给形参InputRadius 这种规则也适用于对象

上一程序当中 MyString类包含了一个指针类型成员 指向动态分配的内存

复制这个类的时候 我们会复制指针 但是不会复制指针指向的缓冲区 也就是导致两个对象指向同一块动态分配的内存

我们称之为浅复制 这会威胁程序的稳定性

比如说下面这个程序

#include<bits/stdc++.h>
using namespace std;
class MyString
{
  private:
    char* buffer;
  public:
    MyString(const char* InitialString)
    {
      if(InitialString!=NULL)
      {
      buffer=new char[strlen(InitialString)+1];
      strcpy(buffer,InitialString);	
      }
      else buffer=NULL;	
    }	
    ~MyString()
    {
      printf("Now we delete.\n");	
      if(buffer!=NULL) delete[] buffer;	
    }
    int getlen(){return strlen(buffer);}
    const char* getstring(){return buffer;}
};
void UseMyString(MyString person)
{
  printf("It has %d characters.\n",person.getlen());
  printf("buffer contains:%s.\n",person.getstring());
  return;
}
int main()
{	
  MyString person("Say Hello To The World");
  UseMyString(person);
  return 0;
} 

运行结果如下:

学习笔记 C++ 类与对象

如果你可以看懂Dev-C++的exe的话 没错RE(runtime error)了

C++中使用delete释放内存块或者指针内存之后 不要进行访问 不能多次释放

由于形参复制实参之后 指针指向的是同一内存 所以void UseMyString(MyString person)在运行结束之后析构函数调用delete释放了这块内存 而主函数运行结束之后析构函数对于同一块内存又释放了一次 所以导致RE

一般来讲编译器会默认进行深复制
但是这里没有 因为编译时ta不确定MyString::buffer指向的是多少字节的内存

所以 我们使用复制构造函数进行深复制

复制构造函数也存在内部声明与外部声明

大概长成这个模样

class MyString
{
  MyString(const MyString& CopySource)
  {	
  }
};
MyString::MyString(const MyString& CopySource)
{
}

具体如下:

#include<bits/stdc++.h>
using namespace std;
class MyString
{
  private:
    char* buffer;
  public:
  MyString(const char* InitialString)
  {
    if(InitialString!=NULL)
    {
      buffer=new char[strlen(InitialString)+1];
      strcpy(buffer,InitialString);	
      cout<<"buffer points to 0x"<<hex<<(unsigned int*)buffer<<endl;
    }
    else buffer=NULL;
  }	
  MyString(const MyString& CopySource)
  {//复制构造函数
    printf("Now we are copying\n");
    if(CopySource.buffer!=NULL)
    {
      buffer=new char[strlen(CopySource.buffer)+1];
      strcpy(buffer,CopySource.buffer);	
      cout<<"buffer points to 0x"<<hex<<(unsigned int*)buffer<<endl;
    }
    else buffer=NULL;
  }
  ~MyString()
  {
    printf("Now we delete.\n");	
    if(buffer!=NULL) delete[] buffer;	
  }
  int getlen(){return strlen(buffer);}
  const char* getstring(){return buffer;}
};
void UseMyString(MyString person)
{
  printf("It has %d characters.\n",person.getlen());
  printf("buffer contains:%s.\n",person.getstring());
  return;
}
int main()
{	
  MyString person("Say Hello To The World");
  UseMyString(person);
  return 0;
} 

复制构造函数可以保证函数调用的时候神采用深复制

但是 当通过复制进行复制时 由于我们没有指定任何赋值运算符 赋值仍然采用浅复制 所以我们还需要制定赋值运算符

MyString::operator =(const MyString& CopySource){}

复制构造函数调用形参时 添加const可防止源对象被修改 同时复制构造函数的参数必须按引用传递 否则调用时将复制形参的值 导致浅复制——这正是我们要极力避免的

所以 类包含原始指针成员(char*等)时,务必编写复制构造函数和复制复制运算符

为了减少工作量 务必将类成员声明为std::string和智能指针类,而非原始指针,因为它们自己实现了复制构造函数

9.结构不同于类的地方

关键字struct来自于C 在C++来看与类及其相似 差别在于程序员未指定时默认的访问限定符(private与public)不同

除非指定了 否则结构中的成员默认为公有的

除非指定了 否则结构以公有方式继承基结构

10.声明友元

对于类中private定义的私有数据成员和方法 我们无法从外部访问 但是这不适用于友元类和友元函数

我们通过关键字friend声明友元 从而使得友元类和友元函数可以访问私有数据成员和方法

#include<bits/stdc++.h>
using namespace std;
class Human
{
  private:
  string Name;
  friend class Unity;//声明友元类
  friend void Display(const Human& person);//声明友元函数
  public:
  Human(string InputName="Adam"):Name(InputName){}
};
class Unity
{
  public:
  static void DisplayAge(const Human& person)
  {cout<<person.Name<<endl;}
};
void Display(const Human& person)
{
  cout<<person.Name<<endl;
}
int main()
{	
  Human cdy;
  Display(cdy);
  Unity::DisplayAge(cdy);
  return 0;
} 

这些基本上是目前整理的 我可能还会不定期更新的

上一篇:第三方库AFNetworking 3.1.0的简单使用


下一篇:014、Java中byte自动转型的操作