C++基础语法——类和对象

一、类的定义

定义方法

在c语言中的结构体,在c++中被叫做类,由于c++兼容c,因此由原先的定义方式是允许的,但在c++中,关于类经常使用的关键字不是struct,而一般用class,而且区别于以前的结构体,在类中不只可以有成员的声明,还可以直接写成员函数,或者是成员函数的声明

class className
{
// 类体:由成员函数和成员变量组成
};  // 一定要注意后面的分号

例如:

//写一个简单的类
class Data
{
	//私有的一般写成员声明
private:
	int _year;//成员声明的变量一般在命名前加个_是为了区别成员函数的形参
	int _month;
	int _day;

public:
	void Init(int year, int month, int day)//类的成员函数可以直接在类里面定义
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		cout << _year << "年";
		cout << _month << "月";
		cout << _day << "日" << endl;
	}
};

int main()
{
	Data n1;
	n1.Init(2023,07,31);
	n1.Print();
	system("pause");
	return 0;
}

访问限定符

上面的例子中涉及到一个语法知识叫访问限定符

访问限定符有三种:public(公用)、protected(保护)、private(私有)

(目前暂时认为保护和私有是一样的)

一般在类里要声明哪一部分是公用的,哪一部分是私有的,公用部分是可以在外部接口调用或者改变的部分,一般是成员函数,私有的则一般是成员声明或者不希望被改变的部分

成员函数的声明与定义分离

在定义类时,除了像上面的直接将定义写到类里,通常更多的是将声明放在类中,函数的定义放在另一个文件中,这里就涉及到一个类的作用域的概念,每一个类都是相对独立的一个域,在定义部分要在函数名前用域作用符去找到相对应的类作用域

例子:

class Person
{
public:
 void PrintPersonInfo();
private:
 char _name[20];
 char _gender[3];
 int  _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
 cout << _name << " "<< _gender << " " << _age << endl;

二、类对象模型

计算类的大小

类中的成员函数不被计算在类的大小中,它被放到另一块公共区域中,类的大小同样遵循着和结构体大小计算的对齐原则,只算成员声明部分,当类是一个空类或者没有成员时,则默认为1,用于占位

C++基础语法——类和对象_第1张图片

this指针

this指针是在调用类函数的过程中,经过编译器的处理,将对象的地址作为隐藏的形参传到函数中,使得类函数能找到相对应的对象进行操作

1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。

2. 只能在“成员函数”的内部使用

3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给 this形参。所以对象中不存储this指针。

4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

三、类的默认成员函数(重要)

1.构造函数

构造函数为了解决对象成员的初始化问题,在定义类时,会默认生成构造函数,默认生成的构造函数对内置类型不做处理,对自定义类型会去调用其相对应的构造函数,构造函数也可以自己定义

构造函数还存在初始化列表,初始化列表用于每个独立的对象成员单独的初始化,所有的对象成员都在初始化列表上进行独立的初始化,定义方式如下

class 类名
{
    public:
    类名()//构造函数
    :对象成员名(初始化的值),
    ...//可初始化多个,用逗号分割
    {函数体}
    
    private:
    对象成员的声明;
}

例子:
class A
{
    public:
    A()
    :_a(1),//初始化列表
    _b(2),
    _c(3)
    {
        //函数体
    }
    private:
    int _a;
    int _b;
    int _c;
}

初始化列表中进行初始化时,会优先采用括号内的值,当括号内不给值或者不进行显式的初始化时,会默认进行初始化,采用缺省值,当缺省值不存在时,遵循自定义类型会调用相关的构造函数,内置类型不做处理

注意:由于一些特别情况,建议每次创建类时,每个对象成员都在初始化列表进行初始化

其特征如下:

1. 函数名与类名相同。

2. 无返回值。

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载。

5. 对象成员的初始化顺序按照声明的顺序来定,与初始化列表的顺序无关。

2.析构函数

析构函数用于自动处理类对象的销毁,默认生成的会对内置类型进行销毁,对自定义类型去调用其本身的析构函数,在一些情况下,用默认生成的析构函数即可,但如果涉及内存操作和管理的类,需要根据具体情况,自己去定义析构函数

析构函数的定义:

~类名()
{
    //函数体;
}

析构函数是特殊的成员函数,其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值类型。

3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构 函数不能重载

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

3.拷贝构造

拷贝构造也是构造函数的一种,它主要解决对象的拷贝问题,默认生成的拷贝构造对内置类型不做处理,对自定义类型回去调用对于的拷贝构造,在自定义拷贝构造函数时,参数要传const &类型,不然会陷入死递归

拷贝构造函数也是特殊的成员函数,其特征如下:

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

3. 若未显式定义,编译器会生成默认的拷贝构造函数。

4. 编译器生成的默认拷贝构造函数只能进行浅拷贝(值拷贝)

5. 拷贝构造函数典型调用场景:

使用已存在对象创建新对象

函数参数类型为类类型对象

函数返回值类型为类类型对象

class Date
{
public:
 Date(int year, int minute, int day)//构造函数
 {
 cout << "Date(int,int,int):" << this << endl;
 }
 Date(const Date& d)//拷贝构造
 {
 cout << "Date(const Date& d):" << this << endl;
 }
 ~Date()//析构函数
 {
 cout << "~Date():" << this << endl;
 }
private://成员对象
 int _year;
 int _month;
 int _day;
};
Date Test(Date d)//拷贝构造
{
 Date temp(d);//拷贝构造
 return temp;//拷贝构造
}
int main()
{
 Date d1(2022,1,13);//构造
 Test(d1);
 return 0;
}

四.运算符重载 operator

函数原型:返回值类型 operator操作符(参数列表)

作用:运算符重载是针对自定义类型在使用运算符时,可以对运算符进行重新定义,使得自定义类型也能根据实际需求去使用运算符,使用的规则也是自己定义

运算符重载可以定义在全局使用,但更多时候是类内部进行定义

不能通过连接其他符号来创建新的操作符:比如operator@

重载操作符必须有一个类类型参数 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义

作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this

注意:     .*    ::    sizeof    ? :   .    这5个运算符不能重载。

五、const成员

定义:const可以用来修饰类成员函数,实际上修饰的是成员函数内的this指针,表明被修饰的成员函数内,不可对成员参数进行任何修改,将const修饰的“成员函数”称之为const成员函数

使用方式:在成员函数后面直接加const,例: void fun(...) const;

作用:在调用类成员函数时,有时候穿参时会使用 const对参数进行修饰,而this指针由于被隐藏,因此可能会存在权限发大的问题,使用该语法是用来补充这点的不足

六、explicit关键字

介绍:对于单参数的构造函数,具有类型转换的功能,在定义时可以自动的进行类型转换,若是不想其自动转换,可以使用explicit关键字去限制,关键字加在函数声明前

七、static成员

概念:

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用 static修饰的成员函数,称之为静态成员函数静态成员变量一定要在类外进行初始化

特性:

1.静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,只进行一次初始化

2.静态成员的定义必须在类外面,定义在全局,定义时不加关键字,用类名+: :去找到声明

3.类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问

4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员

5. 静态成员也是类的成员,受public、protected、private 访问限定符的限制

应用场景之一:判断程序运行后类调用了多少次析构

要判断调用多少次析构,可以在类中定义一个静态变量,对每次调用进行统计

八、友元

1.友元函数

概念:

由于类的封装特性,一般情况下,类外面的函数无法改变和调用类成员参数,在一些特殊的情况下,可能需要调用或者改变类成员参数时,可以在类内对该函数进行友元声明:

格式:     

friend 函数声明;

(一般不建议使用友元函数,因为会破坏类的封装性)

特性:

友元函数可访问类的私有和保护成员,但不是类的成员函数

友元函数不能用const修饰

友元函数可以在类定义的任何地方声明,不受类访问限定符限制

一个函数可以是多个类的友元函数

友元函数的调用与普通函数的调用原理相同

2.友元类

概念:

与友元函数类似,在一个类中对另一个类进行友元声明,则被声明的类即可使用该类的类成员变量

特性:

1.友元关系是单向的,不具有交换性。

比如上述Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接 访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。

2.友元关系不能传递 如果C是B的友元, B是A的友元,则不能说明C时A的友元。

九、部分零零散散的小语法

1.内部类

概念:在类的内部也可以定义一个类,在内部的类可以直接使用外部的成员变量,相当于是外部类的友元类,但外部类不能直接使用内部的

2.匿名对象

概念:在一些情况下需要通过类对象去调用某些函数或者成员作返回值,但又不需要用到该对象本身时,可以使用匿名对象去调用,能够提高效率,匿名对象的生命周期只存在与定义那一行,用完即立刻销毁

3.拷贝时编译器的一些自动优化

概念:在拷贝构造时,如果存在一些重复多余的拷贝构造,则编译器可能会自动帮你优化

例子:

class A
{
public:
 A(int a = 0)
 :_a(a)
 {
 cout << "A(int a)" << endl;
 }
 A(const A& aa)
 :_a(aa._a)
 {
 cout << "A(const A& aa)" << endl;
 }
比特就业课
 A& operator=(const A& aa)
 {
 cout << "A& operator=(const A& aa)" << endl;
 if (this != &aa)
 {
 _a = aa._a;
 }
 return *this;
 }
 ~A()
 {
 cout << "~A()" << endl;
 }
private:
 int _a;
};
void f1(A aa)
{}
A f2()
{
 A aa;
 return aa;
}
int main()
{
 // 传值传参
 A aa1;
 f1(aa1);
 cout << endl;
 // 传值返回
 f2();
 cout << endl;
 // 隐式类型,连续构造+拷贝构造->优化为直接构造
 f1(1);
 // 一个表达式中,连续构造+拷贝构造->优化为一个构造
 f1(A(2));
 cout << endl;
 // 一个表达式中,连续拷贝构造+拷贝构造->优化一个拷贝构造
 A aa2 = f2();
 cout << endl;
 // 一个表达式中,连续拷贝构造+赋值重载->无法优化
 aa1 = f2();
 cout << endl;
 return 0;
}

总结:

本篇内容讲了关于类和对象的部分比较常用的语法,相比于c,在语法上有许多扩展,这些语法在结合具体的例子能更好的理解,建议可以自定义一个日期类进行练习,日期类细节上的搭建也会后续进行补充更新

你可能感兴趣的:(c++,开发语言)