6.定义类(类成员函数、类访问修饰符、构造函数&析构函数、拷贝构造函数、友元函数、内联函数、this指针、指向类的指针、类的静态成员)

文章目录

  • 6.定义类
    • 6.1类成员函数
      • 6.1.1类中的const
        • 常成员
        • 常成员函数
    • 6.2类访问修饰符
      • 公有(public)成员
      • 私有(private)成员
      • 保护(protected)成员
    • 6.3构造函数&析构函数
      • 类的构造函数
      • 默认构造函数
      • 带参数的构造函数
      • 使用初始化列表来初始化字段
      • 构造函数重载
      • 类的析构函数
    • 6.4拷贝构造函数
      • 默认拷贝构造函数
      • 浅拷贝
      • 深拷贝
    • 6.5友元函数
    • 6.6内联函数
    • 6.7C++的this指针
    • 6.8C++中指向类的指针
    • 6.9C++类的静态成员
      • 特殊的整型const static成员
      • 静态成员函数

6.定义类

6.1类成员函数

类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
例如:

class Box
{
   public:
      double length;         // 长度
      double breadth;        // 宽度
      double height;         // 高度
      double getVolume(void);// 返回体积
};

成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。所以您可以按照如下方式定义 Volume() 函数:

class Box
{
   public:
      double length;      // 长度
      double breadth;     // 宽度
      double height;      // 高度
   
      double getVolume(void)
      {
         return length * breadth * height;
      }
};

也可在类的外部使用范围解析符 ::来定义函数。例如:

double Box::getVolume(void)
{
    return length * breadth * height;
}

实例:

#include 
using namespace std;

class Box
{
public:
    double length;   // 盒子的长度
    double breadth;  // 盒子的宽度
    double height;   // 盒子的高度

    double getVolume(void)
    {
        return length * breadth * height;
    }
};

int main()
{
    Box Box1;        // 声明 Box1,类型为 Box

    // box 1 详述
    Box1.height = 5.0;
    Box1.length = 6.0;
    Box1.breadth = 7.0;

    cout << Box1.getVolume() << endl;
    return 0;
}

6.1.1类中的const

常成员

可以在类中定义类的常成员,即不可修改的数据成员。
常成员的初始化必须在构造函数中,并且必须使用初始化表进行初始化。

常成员函数

在定义类的成员函数时,有的函数不改变类的数据成员,也就是说,作为只读函数,而有的函数又要改变数据成员,所以,在函数后面加上const关键字进行标识,提高了程序的可读性,也提高了程序的可靠性,已经定义为const的函数,一旦企图改变类的数据成员时,编译器就会报错。

C++函数前面和后面const的用法:

  • 前面使用const表示返回值为const
  • 后面使用const表示函数不可以修改类的成员
    常成员函数的原理:众所周知,每一个类的成员函数都会被编译器加上一个this指针,一般的this指针是常指针,即指针变量中所存的内存地址不可修改,但是指针所指向的对象的数据成员是可以修改的。但是当成员函数后面加上const关键字时,this变成指向常对象的常指针,即内存地址和所指对象的数据成员均不可修改

6.2类访问修饰符

数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。关键字 public、private、protected 称为访问修饰符。

一个类可以有多个 public、protected 或 private 标记区域。每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。成员和类的默认访问修饰符是 private。

class case:
{
public:
//	公有成员
protected:
//	受保护成员
private:
//	私有成员
}

公有(public)成员

公有成员是在程序中类的外部可以访问的,可以不使用任何成员函数来设置和获取公有变量的值。如下:

#include 
 
using namespace std;
 
class Line
{
   public:
      double length;
      void setLength( double len );
      double getLength( void );
};
 
// 成员函数定义
double Line::getLength(void)
{
    return length ;
}
 
void Line::setLength( double len )
{
    length = len;
}
 
// 程序的主函数
int main( )
{
   Line line;
 
   // 设置长度
   line.setLength(6.0); 
   cout << "Length of line : " << line.getLength() <<endl;
 
   // 不使用成员函数设置长度
   line.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of line : " << line.length <<endl;
   return 0;
}

私有(private)成员

私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。只有类和友元函数可以访问私有成员。
默认情况下,所有类的成员都是私有的。

#include 
 
using namespace std;
 
class Box
{
   public:
      double length;
      void setWidth( double wid );
      double getWidth( void );
 
   private:
      double width;
};
 
// 成员函数定义
double Box::getWidth(void)
{
    return width ;
}
 
void Box::setWidth( double wid )
{
    width = wid;
}
 
// 程序的主函数
int main( )
{
   Box box;
 
   // 不使用成员函数设置长度
   box.length = 10.0; // OK: 因为 length 是公有的
   cout << "Length of box : " << box.length <<endl;
 
   // 不使用成员函数设置宽度
   // box.width = 10.0; // Error: 因为 width 是私有的
   box.setWidth(10.0);  // 使用成员函数设置宽度
   cout << "Width of box : " << box.getWidth() <<endl;
 
   return 0;
}

保护(protected)成员

保护成员和私有成员十分相似,但不同的是,保护成员在派生类(即子类)中是可以被访问的。
如下,父类Box中派生了一个子类smallBox,width成员可以被派生类smallBox中的任何成员函数访问

#include 
using namespace std;
 
class Box
{
   protected:
      double width;
};
 
class SmallBox:Box // SmallBox 是派生类
{
   public:
      void setSmallWidth( double wid );
      double getSmallWidth( void );
};
 
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
    return width ;
}
 
void SmallBox::setSmallWidth( double wid )
{
    width = wid;
}
 
// 程序的主函数
int main( )
{
   SmallBox box;
 
   // 使用成员函数设置宽度
   box.setSmallWidth(5.0);
   cout << "Width of box : "<< box.getSmallWidth() << endl;
 
   return 0;
}

如果类是用struct关键字定义的,则在定义第一个访问标号之前的成员是公有的,如果类是用class关键字定义的,则这些成员都是私有的。

6.3构造函数&析构函数

类的构造函数

类的构造函数是类的一种特殊的成员函数,它在每次创建类的新对象是执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回void,构造函数用于某些成员变量设置初始值

实例:

#include 
using namespace std;
class Box
{
public:
    double width;
    Box();	//创建构造函数
};

Box::Box(void)
{
    cout << "Hello" << endl;
}

int main()
{
    Box box1,Box2;  //创建两个Box的对象,构造函数被执行两次
    box1.width = 1.11;
    cout << box1.width <<endl;
    return 0;
}

//输出:
//Hello
//Hello
//1.11

默认构造函数

例:

class testClass
{
public:
    testClass();                    /* 默认构造函数 */
    testClass(int a, char b);        /* 构造函数 */
    testClass(int a=10,char b='c');    /* 默认构造函数 */

private:
    int  m_a;
    char m_b;
};

默认构造函数完成如下初始化:

testClass classA;
// 或者  testClass *classA = new testClass;

在这种情况下,如果没有提供默认构造函数,编译器会报错;
非默认构造函数在调用时接受参数,如下形式:

testClass classA(12,'H');
//或者  testClass *classA = new testClass(12,'H');

一般的,如果程序员没有自定义默认构造函数,则编译器会自定义默认构造函数,其形如testClass(){};函数体为空,什么都没有。

定义默认构造函数一般有两种,一是定义一个无参的构造函数,而是定义所有参数都有默认值的构造函数。

注意:一个类只能由一个默认构造函数!也就是说两种方式不能同时存在,一般选择形如testClass(){};这种形式的默认构造函数。

实例:

#include 
using namespace std;

class Box
{
public:
    Box()
    {
        cout << "aaa";
    }
    Box(int a = 10, char b = 'c')   //出现错误,因为定义了两个默认构造函数
    {
        cout << "bbb";
    }


};
int main()
{
    Box box1;
    return 0;
}

带参数的构造函数

默认的构造函数是不带参数的,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值

#include 
using namespace std;
class Box
{
public:
    double width;
    double length;
    Box(double len);    //构造函数的声明
};

Box::Box(double len)    //构造函数的定义
{
    cout << "Hello" << endl;
    length = len;
}

int main()
{
    Box box1(2.22);     //设置初始值
    cout << box1.length <<endl;
    return 0;
}

//输出:
//Hello
//2.22

使用初始化列表来初始化字段

Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}

上面的语法等同于如下语法:

Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

假设有一个类C,具有多个字段X、Y、Z等需要初始化,同理可以使用以上语法,只需要在不同字段加逗号进行分隔,如下所示:

C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
  ....
}

构造函数重载

在一个类中可以定义多个构造函数,以便提供不同的初始化的方法,供用户选用。这些构造函数具有相同的名字,而参数的个数或参数的类型不相同,这称为构造函数的重载

注意:

  • 一个类只能有一个默认构造函数。如果在建立对象时选用的是无参构造函数,应注意正确书写定义对象的语句。
  • 尽管在一个类中可以包含多个构造函数,但是对于每一个对象来说,建立对象时只执行其中一个构造函数,并非每个构造函数都被执行。

实例:

#include  
using namespace std; 
class Box {   
public : 
    Box(); //声明一个无参的构造函数   
    //Box(int h); //有1个参数的构造函数 
    //Box(int h,int w); //有两个参数的构造函数 
    Box(int h, int w, int len) :height(h), width(w), length(len);//声明一个有参的构造函数,用参数的初始化表对数据成员初始化
    int volume( );   
private :   
    int height;   
    int width;   
    int length; 
}; 
Box::Box() //定义一个无参的构造函数 
{   
    height=10; 
    width=10; 
    length=10; 
} 
//Box::Box(int h)
//{
//
//}
//
//Box::Box(int h,int w)
//{
//
//}

Box::Box(int h, int w, int len) :height(h), width(w), length(len)
{
}

int Box::volume( )
{   
    return (height*width*length); 
} 

int main( ) 
{   
    Box box1; //建立对象box1,不指定实参  
    cout<<"The volume of box1 is "<<box1.volume( )<<endl;   
    Box box4(15,30,25); //建立对象box4,指定3个实参   
    cout<<"The volume of box4 is "<<box4.volume( )<<endl;   
    return 0; 
} 

类的析构函数

类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。

析构函数名称与类的名称完全相同,只需要在前面加波浪号(~)作为前缀,析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源

实例:

#include 
using namespace std;
class Box
{
public:
    double width;
    double length;
    Box();    //构造函数的声明
    ~Box();   //析构函数声明
};

Box::Box(void)   //构造函数的定义
{
    cout << "Hello" << endl;
}
Box::~Box(void) //析构函数的定义
{
    cout << "bye bye" << endl;
}

int main()
{
    Box box1;
    return 0;
}

//输出:
//Hello
//bye bye

6.4拷贝构造函数

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。它能够将参数的属性拷贝给新的对象,完成新的对象的初始化。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象
  • 复制对象把它作为参数传递给函数
  • 复制对象,并从函数返回这个对象

实例:

#include 
using namespace std;

class Student
{
public:
    Student(string na,int nu);
    Student(Student &s); //拷贝构造函数声明
    void printfo();      //此处的&不能省略
private:
    string name;
    int number;
};

Student::Student(string na, int nu):name(na),number(nu)
{}

Student::Student(Student &s)
{
    name = s.name;
    number = s.number;
}

void Student::printfo()
{
    cout << "name = " << name << "," << "number = " << number << endl;

}

int main()
{
    Student stu1("zhangsan",123);
    Student stu2(stu1); //通用拷贝构造函数,将stu1的数据拷贝给stu2
    stu2.printfo();
    return 0;
}

//输出:
//name = zhangsan, number = 123

默认拷贝构造函数

在我们不知道拷贝构造函数的情况下,进行对象之间的复制初始化的时候,编译器会自动生成一个拷贝构造函数,这就是默认拷贝构造函数。这个函数就是用已有的对象的数据成员的值来对新的对象进行一一赋值,它具有以下形式:

  Rect::Rect(const Rect& r){
        width = r.width;
        length = r.length;
    }

实例:

#include 
using namespace std;

class D 
{
private:
    int a;
public:
    // 构造函数
    D(int b) : a(b) 
    {}

    // 普通成员函数
    void show() 
    {
        cout << "a = " << a << endl;
    }
};

int main()
{
    D a(666);
    //D b(a); //编译器自动生成默认拷贝构造函数,第一种形式
    D b = a;    //第二种形式
    b.show();

#include 
using namespace std;

class Rect 
{
public:
    Rect() 
    {
        count++;
    }
    ~Rect() {
        count--;
    }
    static int getCount() 
    {  // 静态成员函数
        return count;
    }
private:
    int width;
    int length;
    static int count;  // 静态成员变量count来计数
};

int main()
{
Rect rect1;
cout << "The count of Rect: " << Rect::getCount() << endl;

Rect rect2(rect1);  // 新的对象需要使用老的对象来进行初始化
cout << "The count of Rect: " << Rect::getCount() << endl;

}
//输出:
//The count of Rect:1
//The count of Rect:1

此代码说明:默认的构造函数没有处理静态数据成员

浅拷贝

浅拷贝:指的是在对象复制时,只对对象进行简单的赋值,默认拷贝构造函数执行的也是浅拷贝,大多数时候,浅拷贝就能很好的完成任务了,但是一旦对象中存在有动态成员时,就会出现问题。

6.定义类(类成员函数、类访问修饰符、构造函数&析构函数、拷贝构造函数、友元函数、内联函数、this指针、指向类的指针、类的静态成员)_第1张图片
6.定义类(类成员函数、类访问修饰符、构造函数&析构函数、拷贝构造函数、友元函数、内联函数、this指针、指向类的指针、类的静态成员)_第2张图片

深拷贝

简单的来说,当数据成员中包含动态成员时,浅拷贝只能拷贝指针中所存的内存地址,这样在内存地址的释放时就会出现二次释放异常。而深拷贝会重新开辟内存,不同的动态成员指向不同的内存地址。二者的差异在于浅拷贝后两个不同的动态成员指向相同的内存地址,而深拷贝后,两个不同的动态成员指向不同的内存地址,这样就不会出现内存地址的二次释放异常。
6.定义类(类成员函数、类访问修饰符、构造函数&析构函数、拷贝构造函数、友元函数、内联函数、this指针、指向类的指针、类的静态成员)_第3张图片
6.定义类(类成员函数、类访问修饰符、构造函数&析构函数、拷贝构造函数、友元函数、内联函数、this指针、指向类的指针、类的静态成员)_第4张图片

6.5友元函数

类的友元函数定义在类的外部,但可以访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型在类的定义中出现过,但友元函数不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,该情况下,整个类及其所有成员都是友元。如果声明函数为一个类的友元,需要在类定义中该函数原型前加关键词friend。如下:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

声明类ClassTwo的所有成员函数为类ClassOne的友元,需在类ClassOne的定义中放如下声明:

friend class ClassTwo;

友元函数实例:

#include 
using namespace std;

class Box
{
public:
    void setlength(double len); //声明成员函数设置length的值
    friend void printflength(Box box);    //声明友元函数访问私有成员

private:
    double length;
};

void Box::setlength(double len) //成员函数的定义
{
    length = len;
}

void printflength(Box box)    //定义友元函数
{
    cout << box.length << endl;
}
int main()
{
    Box box;
    box.setlength(1.11);
    printflength(box);

    return 0;
}

友元类实例:

#include 
using namespace std;

class A
{
public:
    void setlength(double len);
    friend class B; //声明A的友元类B

private:
    double length;
};

void A::setlength(double len)   //定义A的成员函数设置length的值
{
    length = len;
}
class B //定义友元类B
{
public :
    void f(A a);    //声明B的成员函数,访问A的私有成员
};

void B::f(A a)  //定义B的成员函数
{
    cout << a.length;
}



int main()
{
    A a;
    B b;
    a.setlength(1.11);
    b.f(a); //使用友元类的成员函数访问私有成员

    return 0;
}

6.6内联函数

C++内联函数通常和类一起使用,如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。如果已定义的函数多于一行,编译器会忽略 inline 限定符。
在类定义中的定义的函数都是内联函数,即使没有使用 inline 说明符。

6.7C++的this指针

我们可以想这样一个问题,当一个新的对象被创建时,编译器不会开辟一段新的内存给成员函数,因为成员函数是公有的,所有对应类的对象都可以访问,那么编译器是怎么知道哪个对象调用了该函数呢?这就是因为C++的this指针。

在编译之后,编译器会在成员函数中自动创建一个this指针,该指针存储该函数所属对象的地址,该指针指向调用该函数所属的对象。

在C++中,每一个对象都会通过this指针来访问自己的地址。this指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。
友元函数没有this指针,因为友元不是类的成员。只有成员函数才有this指针。

注意,this 是一个指针,要用**->**来访问成员变量或成员函数

this指针的主要用途:

  • 在函数中把this指针当参数(可以用来区分二义性)
void setr(double r)
{
	this->r = r;	//this指向对象,以此来区分二义性
}
  • 从函数中返回,用作返回值
return this;	//返回对象地址
return *this;	//返回对象本身

实例:

#include 
using namespace std;

#define PI 3.14159

class Circle
{
public:
    int f()
    {
        return (*this).r;
        //return this->r;
    }
    void setr(int r_);  //设置半径
    int getr(); //获取半径
    double area(); //计算面积
    double perimeter();    //计算周长
private:
    int r;
};
                        //此处注释代码不可加,会产生编译错误,编译器会自动添加该代码
void Circle::setr(int r_/* *this */)    //当c对象调用函数时,this = &c,*this<->c
{
    //this->r = r_; //此处this可加可不加
    (*this).r = r_;
}

int Circle::getr()
{
    return r;
}

double Circle::area()
{
    return PI * r * r;
}

double Circle::perimeter()
{
    return 2 * PI * r;
}
int main()
{
    Circle c;
    c.setr(2);
    /*cout << "area = " << c.area() << "," << "perimeter = " << c.perimeter() << endl;*/
    cout << c.f() << endl;
    return 0;
}

6.8C++中指向类的指针

指向类的指针与指向结构的指针相似,访问指针指向的成员时,需要使用成员访问运算符**->**.和正常指针一样,使用指针前,需要对指针进行初始化。
实例:

#include 
using namespace std;

class Box
{
public:
    Box(double l = 2.0, double b = 2.0, double h = 2.0)
    {
        cout << "I love C++!" << endl;
        length = l;
        breadth = b;
        height = h;
    }
    double Volume(void)
    {
        return length * breadth * height;
    }
private:
    double length;     // Length of a box
    double breadth;    // Breadth of a box
    double height;     // Height of a box
};
int main()
{
    Box box1(1.0,2.0,3.0);
    Box* ptrbox;
    ptrbox = &box1; //保存对象的地址
    cout << ptrbox->Volume() <<endl;    //使用成员访问运算符来访问成员
    return 0;
}

//输出:
//I love C++!
//6

6.9C++类的静态成员

可以使用static关键字把类成员定义为静态的。当类的成员为静态是,无论创建多少个对象,静态成员都只有一个副本。

静态成员在类的所有对象中是共享的。如果不存在其他初始化语句,在创建第一个对象时,静态数据会初始化为0.

不能把静态成员的定义放到类的定义中,可以使用解析运算符::来重新声明静态成员从而进行初始化。(在类的静态成员使用前必须进行定义)

实例:

#include 
using namespace std;

class Box
{
public:
    static int objectCount; //静态成员第一次声明
    Box(double l = 2.0, double b = 2.0, double h = 2.0):length(l),breadth(b),height(h)
    {
        cout << "I love C++!" << endl;
        objectCount++;  //每次创建对象时增加1
    }
    double Volume(void)
    {
        return length * breadth * height;
    }
private:
    double length;     // Length of a box
    double breadth;    // Breadth of a box
    double height;     // Height of a box
};

int Box::objectCount = 2;   //初始化类Box的静态成员
int main()
{
    Box box1(1.0,2.0,3.0);
    Box box2(4.0,5.0,6.0);
    cout << Box::objectCount <<endl;    //使用静态成员
    return 0;
}

//输出:
//I love C++!
//I love C++!
//4

特殊的整型const static成员

一般的,类的static成员像普通数据成员一样,不能在类的的定义中初始化,这一规则的例外是整型const static成员可以在类的定义中初始化。
例如:

class Data
{
public:
    static const int a = 1;
};

静态成员函数

  • 如果将函数成员声明为静态的,就可以把函数与特定的对象分离开,即没有创建对象也可以使用静态成员函数,只需要用范围解析符::即可访问。

  • 静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。

  • 静态成员函数有一个类范围,不能访问类的this指针,平时可以适应静态成员函数来判断类的某些对象是否已被创建。

静态成员函数与普通成员函数的区别:

  • 静态成员函数没有this指针,只能访问静态成员**(包括静态成员变量和静态成员函数 **
  • 普通成员函数有this指针可以访问类的任意成员
#include 
using namespace std;

class Box
{
public:
    static int objectCount; //静态成员第一次声明

    Box(double l = 2.0, double b = 2.0, double h = 2.0):length(l),breadth(b),height(h)
    {
        cout << "I love C++!" << endl;
        objectCount++;  //每次创建对象时增加1
    }

    static int getCount()   //定义静态成员函数
    {
        return objectCount;
    }
    double Volume(void)
    {
        return length * breadth * height;
    }
private:
    double length;     // Length of a box
    double breadth;    // Breadth of a box
    double height;     // Height of a box
};

int Box::objectCount = 0;   //初始化类Box的静态成员

int main(void)
{
    //在创建对象前对象的总数
    cout << "before:" << Box::getCount() <<endl;
    Box box1(1.0,2.0,3.0);
    Box box2(4.0,5.0,6.0);
    cout << "after:" << Box::getCount()<<endl;    //使用静态成员函数
    return 0;
}

//输出:
//before:0
//I love C++!
//I love C++!
//after : 2

你可能感兴趣的:(C++入门经典学习笔记)