系列笔记
  • [基本数据结构]()
  • [类的使用]()
  • [类的继承和派生]()
  • [面向对象特征之一——多态]()
  • [操作符重载]()
  • [const关键字的使用]()

1. 类的定义与使用

1.1 类的定义

最简单的类的申明:
class Student{

};

在类的定义最后需要加上分号,这是和java不同的地方,否则会有编译错误。

1.2 类的使用

即创建类的实例——对象。在java中你会使用到new关键字,而在c++中,创建对象与使用普通的数据类型一样简单:

Student studentObject;

2. 类的成员变量和成员函数

2.1 成员变量

class Student
{
    char name[20];  //姓名
    int id_num;     //学号
    int age;        //年龄
    char sex;       //性别

和java不同,类的成员变量不能在创建的时候初始化值。

2.2 成员函数

和java不同,c++的成员函数有两种定义方式:

1.类内部申明,类外定义(实现):
class student
{
    void set_age(int a);
    int get_age();
};
//在类外部定义set_age函数
void student::set_age(int a)
{
    age = a;
}
//在类外部定义get_age函数
int student::get_age()
{
    return age;
}

在类内部申明函数,在类的外部定义的时候通过作用域操作符 :: 进行实现。
申明的时候,只需写出返回类型、函数名称、参数列表、末尾分号即可,不能加上空的大括号。

    返回类型 类名::成员函数名(参数列表)
    {
        //函数体
    }

你也可以在类的成员函数申明的前面加上inline关键字使其变成内联函数。内联函数可以优化程序的执行效率。

class student
{
    inline void set_age(int a);
    inline int get_age();
};
//在类外部定义set_age函数
void student::set_age(int a)
{
    age = a;
}
//在类外部定义get_age函数
int student::get_age()
{
    return age;
}
2. 类内部边申明边定义(实现)
class Student{
    void set_age(int a){age = a;}
    int get_age(){return age;}
}

这种方式其实和java的成员函数调用方式是相同的。

2.3 访问限制

和java的语法类似,c++也提供了三种关键字:

  • public:本类和其他类都可以访问到
  • private: 只有本类可以访问到
  • protected:只有本类和派生的类可以访问。

但是使用的语法格式有点不同。

class book
{
public:
    void setprice(double a);
    double getprice();
private:
    double price;
};

通过这种格式,可以将属性为public的变量或函数申明在一起。避免了重复的写权限限制符号。

当然,权限相同的成员也可以分开声明,申明的顺序也是无所谓的。

2.4 访问成员变量/函数

创建的对象就是类的实例,就直接使用.点选择符,这里是和java一样的。

创建的如果是对象的指针,需要使用->箭头选择符访问成员。java中没有指针就没有这条规则。

#include <iostream>
using namespace std;
class book
{
public:
    void setprice(double a);
    double getprice();
private:
    double price;
};
void book::setprice(double a)
{
    price = a;
}
double book::getprice()
{
    return price;
}
int main()
{
    book Alice;
    Alice.setprice(29.9);
    cout<<"The price of Alice is $"<<Alice.getprice()<<endl;   
    book *Harry = new book;
    Harry->setprice(49.9);
    cout<<"The price of Harry is $"<<Harry->getprice()<<endl;
    return 0;
}

注意:C++类class和结构体struct区别

class和struct都可以定义一个类的结构。

  • 采用struct关键字,结构体中定义的成员变量或成员函数默认都是public属性的。
  • 使用class关键字,类中定义的成员变量或成员函数默认都是private属性的,

除此之外,没有其他任何区别。

3. 构造函数

3.1 构造函数的定义

构造函数的声明与定义的方式与普通函数是相同的,也是有两种方式,但是有以下几点特殊地方:

  • 构造函数的函数名必须与类名相同;
  • 构造函数无返回值;
  • 当我们创建类对象的时候构造函数会被自动调用,而无需我们主动调用。

这三点和java中语法也是一样的。

通常如果在定义类的时候,没有定义任何一个构造函数的时候,系统会自动生成一个默认构造函数。默认构造函数就是不带任何参数的构造函数。其它带参数的构造函数统称为带参构造函数。

如果在类中声明了任何一个构造函数,则系统不会自动生成默认构造函数。

如果在创建对象时候表示使用哪个构造函数呢?只要在对象名称后加一对小括号,小括号类参数与对应的构造函数参数对应即可:


class book
{
public:
    book(){}
    book(char* a, double p);
    
private:
    double price;
    char[] title;
};

book::book(char[] a, double p)
{
    title = a;
    price = p;
}

int main()
{
    book Harry("Harry Potter", 49.9);
   return 0;
}

3.2 初始化参数表

除了通过在构造函数的函数体内进行成员变量的初始化,还可以通过在构造函数的后面以下面的格式初始化参数

构造函数(参数列表):成员变量1(初始值),成员变量2(初始值)……{}

举个例子:

class book
{
public:
    book(){}
    book(char *a, double p):title(a),price(p){}
};

对,你没有看错,还有这种类似函数的方式给成员变量赋值。

这种写法本质就是为了简写成员变量的赋值操作,你同样可以用普通的方式定义构造函数:


    book(char *a, double p){
        title = a;
        price = p;
    }

在java语法中,你可以在初始化块中给成员变量中赋值,很容易理解。
但是在c++中提供这样一种以成员变量名作为函数名,参数列表是变量的值的形式初始化成员变量。

值得注意的是:参数初始化顺序与初始化表列出表量的顺序无关,参数初始化顺序只与成员变量在类中声明的顺序有关

3.3 构造函数的参数默认值

PHP中的函数参数可以在调用之前就可以赋值一个默认值,因为PHP不支持函数的重载。通过这种方式,当你调用的函数时候没有给其他参数赋值,其他函数也会有一个默认值:

function exampleFunction($parm1,$parm2 = "world"){
    echo $parm1 ." ". $parm2;
}

exampleFunction("hello")
//输出: hello world;

上面的代码可以很清楚的看到,第二个参数已经被赋值了默认值world,当你没有给定第二个参数的时候,第二个参数仍然是有值的。

c++的构造函数也支持参数默认赋值,比如:

class book
{
public:
    book(){}
    book(char* a, double p = 5.0);
private:
    double price;
    char * title;
};
book::book(char* a, double p)  //在定义函数的时候可以不指定默认参数
{
    title = a;
    price = p;
}

此时,当我们这样创建对象的时候:

Book Time("Time book")

调用的就是第二个构造函数,并且price自动赋值为默认值5.0

但默认参数的构造函数会与某些构造函数重载冲突:

book(char* a);

如果我们再申明这样一个构造函数,之前创建对象就会发生编译错误,因为编译器不知道调用哪一个构造函数了。

换句话说,C++在支持构造函数的重载的条件下,基本上没必要使用带默认参数的构造函数

* 3.4 转型、拷贝构造函数

3.4.1 转型构造函数

构造函数关于是否有参数可以分为:

  • 不带参数构造函数
  • 带参数构造函数(含带默认参数的构造函数)

其中带参数构造函数中有两种特殊的构造函数:

  • 转型构造函数
  • 拷贝构造函数

转型构造函数和普通的构造函数没啥区别,就是参数列表只有一个参数,创建对象的时候如果调用该构造函数,就是意味着将参数转换为了一个类的对象,这没什么特别的。

特别之处在于这里有一个隐式的类型转换:比如你创建一个字符串变量,但是你将该变量传入了一个参数为类对象的函数,这个时候就会自动的发生字符串到类对象的隐式对象类型转换

class student
{
public:
    student(char * n){name = n;}//转型构造函数
private :
    char * name;
}


void fun(Student studentObj){};

char * name = “Harry Potter”;
fun(name);
 

这个时候字符串name自动转换为student类的对象,转换的过程中自动调用转型构造函数。

隐式转换会带来调试的困难,可以在构造函数前加上explict关键字,避免这种自动转换。

换句话说,转型构造函数就是参数只有一个的一类构造函数,能实现将参数转换为类对象的功能。

3.4.3 拷贝构造函数

4. 析构函数

在Java中无需自行设计析构函数,java有一套系统级的垃圾回收管理程序。

在创建对象的时候系统会自动调用构造函数,在对象需要被销毁的时候,系统也会自动调用析构函数。

与构造函数类似,析构函数也是一个成员函数,但是有如下特殊之处:

  • 无返回值
  • 没有参数,不能被重载,因此一个类也只能含有一个析构函数
  • 函数名必须为“~类名”的形式,符号“~”与类名之间可以有空格

5. 类的高级用法

5.1 常量指针this

与java类似,在类的每个成员函数中包含一个常量指针,该指针指向调用该函数的对象。

与java类似,该指针只要是用来区分同名的成员变量与参数名称的:

void setprice(double price)
{
    price = price;
}

5.2 new和delete操作符

c++创建对象有两种方式,一种就是普通的直接通过类名 对象名(构造函数参数列表) 创建,另一种就是通过new关键字创建。

两种方式创建对象的空间分配方式有分别。

  • 第一种空间分配在栈中,由系统可以直接管理,比如在代码块外,块内的对象就会被清理。
  • 第二种的空间分配在堆上,大小可以很大, 但是销毁得由程序本身处理。

使用 new 关键字会自动调用构造函数
使用 delete 关键字会自动调用析构函数

5.3 const关键字

const表示一些不希望被人们修改的数据,与java语法中的final static 有类似之处,但是也有很多区别。

const关键字可以定义在成员变量、成员函、对象、对象的常引用上。

5.3.1 修饰(成员)变量

这个很好理解,在变量最前面加上关键字表示初始化之后禁止二次修改。const成员变量的初始化只能通过参数初始化表进行初始化

 const int a=5;

5.3.2 修饰成员函数

const成员函数可以使用类中的所有成员变量,但是不能修改任何成员变量,也不能调用类中任何非const成员函数

fun(形参) const{}

5.3.3 修饰类对象

用const修饰的类对象,该对象内的任何成员变量都不能被修改。

因此不能调用该对象的任何非const成员函数,因为对非const成员函数的调用会有修改成员变量的企图。

class A
{
 public:
    void funcA() {}
    void funcB() const {}
};

int main
{
    const A a;
    a.funcB();    // 可以
    a.funcA();    // 错误

    const A* b = new A();
    b->funcB();    // 可以
    b->funcA();    // 错误
}

5.3.4 修饰类对象的引用

我们在有些函数参数中会使用到对象,这个时候我们不是直接将对象做为参数,而是使用对象的引用。

此时我们在对象的引用前加上关键字,避免函数对对象本身做出任何修改:

void display(const book &b)
{
        b.setprice(59.9);  //compile error
    cout<<"The price of "<<b.gettitle()<<" is $"<<b.getprice()<<endl;  //ok
}

5.3.5 重载成员函数

参考文章: C++ const关键字总结

5.4 static关键字

好了,这个关键字可以说是和java一致的了。

5.4.1 静态成员变量

该变量是同步于类的所有对象,也就是类的变量

5.4.2 静态成员函数

5.5 友元函数和友元类

5.5.1 友元函数

我们知道对象是无法直接调用私有变量或者私有函数的,只有在类中的函数才可以使用类的私有变量或者私有函数。

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

friend  返回类型 函数名(形式参数);
  • 友元函数的声明可以放在类的私有部分,也可以放在公有部分,它们是没有区别的,都说明是该类的一个友元函数。
  • 一个函数可以是多个类的友元函数,只需要在各个类中分别声明。
  • 友元函数的调用与一般函数的调用方式和原理一致。

5.5.2 友元类

友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。

当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。定义友元类的语句格式如下:

friend class 类名;

当你在一个类中申明另一个类为该类的友元的时候,表明该友元类的所有成员函数都可以使用该类的所有信息。这种顺序是单向的。

其中:friend和class是关键字,类名必须是程序中的一个已定义过的类。

例如,以下语句说明类B是类A的友元类:

      class A
      {
             …
      public:
             friend class B;
             …
      };

经过以上说明后,类B的所有成员函数都是类A的友元函数,能存取类A的私有成员和保护成员。

使用友元类时注意

  • 友元关系不能被继承。
  • 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
  • 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明

参考文章:

C++友元函数和友元类

最后修改:2019 年 02 月 24 日
喜欢我的文章吗?
别忘了点赞或赞赏,让我知道创作的路上有你陪伴。