C++类和对象

目录

1.类和对象的理解

1.1 概念

1.2 类的访问限定符

1.3 类的成员

1.4 类的使用实例

2.类的对象细致讲解

2.1默认成员函数介绍

构造函数

析构函数

拷贝构造函数

赋值运算符重载

2.2匿名对象

2.3 this指针

3.静态成员变量和静态成员函数

3.1静态成员变量

3.2静态成员函数

4.友元函数与友元类

4.1友元函数

4.2友元类


1.类和对象的理解

1.1 概念

C++是一个面向对象的语言,要了解学透C++,我们要先理解类和对象。

C++兼容C语言,结构体可以继续使用,类可以看做是结构体的升级版。

结构体:默认访问是公有的,结构体外部可以访问其内部。

类:默认访问是私有的,类的外部无法访问其内部,但可以自己设定为公有,让其访问。

以下是关于class(类)的说明

1.类的定义尾部有一个分号,代表定义结束,不能删掉!

2.一个类可以创建多个对象,每个对象都是变量。

3.类里面还可以定义一个新的类,称作为子类。

4.计算类的大小和结构体一样,也是需要字节对齐。

5.访问类可以通过.或者->进行访问。

struct student
{
	int age;
	char* name;
};
class student1
{
public:
	class student2   //类中定义另一个类
	{
	public:
		void Print()
		{
			cout << sex << endl;
		}
	public:
		const char* sex;
	};
	void Print()
	{
		cout << "年龄是:" << age <<"  " << "姓名是:" << name << endl;
	}
public:
	int age;
	const char *name;
};
int main()
{
	student s1;
	s1.age = 10;
	
	student1 s2;
	s2.name = "小明";
	s2.age = 13;
	s2.Print();

	student1::student2 s3;
	s3.sex = "男";
	s3.Print();

	cout << sizeof(student) << endl;
	cout << sizeof(student1) << endl;
	
	return 0;
}

1.2 类的访问限定符

public:公有属性,在它的限定下的变量或者函数,类的内部和外部都能访问。

private:私有属性,在它的限定下的变量或者函数,只有类的内部能访问,类的外部不能访问。

protect:保护属性,在它的限定下的变量或者函数,只有类的内部或者子类才能访问。

1.3 类的成员

类有成员变量和成员函数。

并且对象是有类创建的。

class student
{
public:
	void Print()
	{}//成员函数
private:
	int age;
	const char* name;   //成员变量
};
int main()
{
	student s1;  //s1即为student类创建的对象
	return 0;
}

1.4 类的使用实例

创建一个Date类

Date.h

#include <iostream>
using namespace std;
class Date
{
public:
	void Init(int year=2024,int month=7,int day=13);
	void Print();
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

#include "Date.h"
void Date::Init(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
void Date::Print()
{
	cout << _year << "年" << _month << "月" << _day << "日" << endl;
}

test.cpp

int main()
{
	Date d1;
	d1.Init();
	d1.Print();

	Date d2;
	d2.Init(2023, 7, 13);
	d2.Print();

}

2.类的对象细致讲解

什么是默认成员函数,是不用写。编译器能自动生成的函数

2.1默认成员函数介绍

构造函数

  • 构造函数的函数名和类名是相同的 (比如类名是 Date,构造函数名就是 Date)。
  • 构造函数无返回值 (它不具有返回类型,因此不能直接返回值)。
  • 构造函数支持重载(下面有例子)。
​
Date() {             //无参构造函数
	_year = 0;
	_month = 1;
	_day = 1;
}

Date(int year=,int month=,int day=)//有参构造函数
{
	cout << "Date()" << endl;
	_year = year;
	_month = month;
	_day = day;
}

​

创建一个对象的d1,,当我们调用构造函数的时候,我们不能写d1.Date(),构造函数不是常规的成员函数,这样会显示报错。

如果你没有自己定义构造函数(类中未显式定义),C++ 编译器会自动生成一个无参的默认构造函数。当然,如果你自己定义了,编译器就不会帮你生成了。

#include <iostream>
 
class Date {
public:
    
    void Print() {
        cout<<_year<<_month<<_day<<endl;
    }
 
private:
    int _year;
    int _month;
    int _day;
};
 
int main(void)
{
    Date d1;  // 这里调用的是默认生成的无参的构造函数
    d1.Print();
 
    return 0;
}

运行结果如下:

 没有定义构造函数,对象也可以创建成功,因此此处调用的是 编译器默认生成的构造函数。

析构函数

析构函数也是特殊的成员函数

  • 析构函数名是在类名前面加上字符 ~ 
  • 析构函数通俗的将就是帮我们“清理垃圾用的”。
  • 析构函数既没有参数也没有返回值(因为没有参数,所以也不会构成重载问题)
  • 一个类的析构函数有且仅有一个(如果不写系统会默认生成一个析构函数)
  • 析构函数在对象生命周期结束后,会自动调用。
  • 有一个特点,后定义的先析构

演示自动调用,在调用析构函数的时候"吱”一声

using namespace std;
class Date {
public:
    Date(int year = 1, int month = 0, int day = 0) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }

    ~Date() {
        cout << "~Date() 吱~ " << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main(void)
{
    Date d1;
    Date d2(2022, 3, 9);

    return 0;
}

运行结果如下:

  • 对于 "内置类型" 的成员变量:不会做初始化处理。
  • 对于 "自定义类型" 的成员变量:会调用它的默认构造函数(不用参数就可以调的)初始化,如果没有默认构造函数(不用参数就可以调用的构造函数)就会报错!

拷贝构造函数

  • 拷贝构造函数是构造函数的一个重载。
  • 参数只有一个,必须是同类型的对象。
  • 必须要引用传参。

我们在创建对象的时候,能不能创建一个与某一个对象一模一样的新对象呢?

Date d1(2022, 3, 9);    
d1.Print();
 
Date d2(d1);    // 照着d1的模子做一个d2
d2.Print();

 当然可以,这里我们就要用到拷贝构造。

    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

只有单个形参,该形参是对本类类型对象的引用(一般常用 const 修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

代码演示如下:

#include <iostream>
using namespace std;
class Date {
public:
    Date(int year = 1, int month = 0, int day = 0) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
    Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }

    ~Date() {
        cout << "~Date() 吱~ " << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main(void)
{
    Date d1;
    Date d2(2022, 3, 9);

    Date d3(d2);
    d3.Print();

    return 0;
}

运行结果:

是不是完美复刻了d2的值,并且三个对象调用了三次析构函数

当然有一个重点呢!

拷贝构造为什么要用到引用传参呢?

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

调用拷贝构造,需要先穿参数,传值传参又是一个拷贝构造。

……

一直在传参这里出不去了,所以这个递归是一个无穷无尽的。

#include <iostream>
using namespace std;
class Date {
public:
    Date(int year = 1, int month = 0, int day = 0) {
        _year = year;
        _month = month;
        _day = day;
    }
    void Print() {
        printf("%d-%d-%d\n", _year, _month, _day);
    }
    /*Date(const Date& d)
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }*/

    ~Date() {
        cout << "~Date() 吱~ " << endl;
    }

private:
    int _year;
    int _month;
    int _day;
};

int main(void)
{
    Date d1;
    Date d2(2022, 3, 9);

    Date d3(d2);
    d3.Print();

    return 0;
}

当我们省略了拷贝构造函数的时候,它还是拷贝出来了。那是不是代表我们不再需要些拷贝构造,编译器会自动帮我们写好。

当然是不行的,因为有些情况是不可避免的。

比如实现栈的时候,栈的结构问题,导致这里如果用默认的 拷贝构造,会翻车。

按字节把所有东西都拷过来会产生问题,如果 Stack st1 拷贝出另一个 Stack st2(st1

会导致他们都指向那块开辟的内存空间,导致他们指向的空间被析构两次,导致程序崩溃

当遇到这种情况的时候,我们要给栈一个深拷贝

防止这块空间被析构两次

class stack
{
public:
	stack(size_t n = 4)
	{
		cout << "stack()" << endl;
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("malloc fail");
				exit(-1);
			}
			top = 0;
			capacity = n;
		}
	}
	stack(stack& s)
	{
		a = (int*)malloc(sizeof(int) * s.capacity);
		if (a == NULL)
		{
			perror("malloc申请空间失败");
			return;
		}
		memcpy(a, s.a, sizeof(int) * s.top);
		top = s.top;
		capacity = s.capacity;
	}
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}
	void push(int x)
	{
		if (top == capacity)
		{
			int newcapacity = capacity == 0 ? 4 : capacity * 2;
			int* tmp = (int*)realloc(a, sizeof(int) * newcapacity);
			if (tmp == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}
			if (tmp == a)
			{
				cout << "原地扩容" << endl;
			}
			else
			{
				cout << "异地扩容" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}

	void pop()
	{
		assert(top > 0);
		top--;
	}
	int Top()
	{
		return a[top - 1];
	}
	bool Empty()
	{
		return top == 0;
	}
	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}
private:
	int* a;
	int top;
	int capacity;
};
int main()
{
	stack s1;
	s1.push(1);
	s1.push(2);
	s1.push(3);
	
	stack s2(s1);
	
	return 0;
}

赋值运算符重载

运算符重载包含opreator<  operator>  operator== operator+= 等等

这些运算符能够帮助我们进行日期类的对比

​
bool Date::operator<(const Date& d)
	{
		if (this->_year < d._year)
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month)
		{
			return true;
		}
		else if (this->_year == d._year && this->_month == d._month && this->_day < d._day)
		{
			return true;
		}
		return false;
	}
	bool Date:: operator==(const Date& d)
	{
		return this->_year == d._year
			&& this->_month == d._month
			&& this->_day == d._day;
	}
	bool Date:: operator<=(const Date& d)
	{
		return *this < d || *this == d;
	}
	int Date::Getmonthday(int year, int month)
	{
		int MonthArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
		if (month == 2 && year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
		{
			return 29;
		}
		return MonthArr[month];
	}


	Date& Date::operator+=(int day)
	{
		_day += day;
		while (_day > Getmonthday(_year, _month))
		{
			_day -= Getmonthday(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	Date Date::operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}

	Date& Date:: operator=(const Date& d)
	{
		this->_year = d._year;
		this->_month = d._month;
		this->_day = d._day;
		return *this;
	}

	Date& Date::operator-=(int day)
	{
		_day -= day;
		while (_day <= 0)
		{
			_month--;
			if (_month ==0)
			{
				_year--;
				_month = 12;
			}
			_day += Getmonthday(_year, _month);
		}
		return *this;
	}

	Date Date::operator-(int day)
	{
		Date tmp(*this);
		tmp -= day;
		return tmp;
	}

	Date& Date::operator++()
	{
		return *this += 1;
	}

	Date Date::operator++(int)
	{
		Date tmp(*this);
		*this += 1;
		return tmp;
	}

	Date& Date::operator--()
	{
		return *this -= 1;

	}

	Date Date::operator--(int)
	{
		Date tmp(*this);
		*this -= 1;
		return tmp;
	}
	int Date::operator-(const Date& d)
	{
		Date max = *this;
		Date min = d;
		int flag = 1;
		if (*this < d)
		{
			max = d;
			min = *this;
			flag = -1;
		}
		int n = 0;
		while (min<max)
		{
			n++;
			min++;
		}
		return n * flag;
	}

​

2.2匿名对象

类名()//匿名对象,代码执行完毕,立即释放(不用到return)

#include <iostream>
using namespace std;
class Test
{
    public:
        Test()
        {
            cout<<"Test的无参构造函数"<<endl;
        }
        ~Test()
        {
            cout<<"Test的有参构造函数"<<endl;
        }
};
int main(int argc, char const *argv[])
{
    Test();   //匿名对象,本行代码执行完,立即被释放
    cout<<"*******************"<<endl;
    return 0;
}

2.3 this指针

this是一个形参,一般存在栈桢里。

在同一个类中,this指向的同一个地址。

this的底层是指针。

在运算符重载里面,作为成员函数重载时,形参看起来比操作数数目少1,因为有隐藏的参数this

​
bool Date::operator<(const Date& d)
	{
		if (this->_year < d._year)
		{
			return true;
		}
		else if (this->_year == d._year && this->_month < d._month)
		{
			return true;
		}
		else if (this->_year == d._year && this->_month == d._month && this->_day < d._day)
		{
			return true;
		}
		return false;
	}


​

3.静态成员变量和静态成员函数

3.1静态成员变量


(1)普通函数可以访问静态成员变量

(2)静态成员变量一定要在类的外部初始化

(3)静态成员变量可以直接通过类名来访问

在c++中,静态成员变量属于某个类,而不属于某个对象,我们可以使用静态成员变量来实现多个对象共享数据的目标

class Student

{

        static int m_num;

};

3.2静态成员函数

(1)静态成员函数中只能访问静态成员变量,不能访问普通变量

(2)静态成员函数中的静态成员变量可以通过类名访问

(3)普通成员函数不能通过类名访问

#include <iostream>
using namespace std;
class Student
{
    public:
        static int count;
    private:
        int id;
    public:
        Student()
        {
            count++;
            id = count;
        }
        int GetCount() //普通函数可以访问静态成员变量
        {
            return count;   
        }
        static int GetCount1() //静态成员函数
        {
            return count;    //静态成员函数中只能访问静态成员变量,不能访问普通变量
        }
};
int Student::count = 0;  //静态成员变量一定要在类的外部初始化
int main(int argc, char const *argv[])
{
    Student s1;
    Student s2;
    cout<<s1.count<<endl;
    cout<<s2.count<<endl;
    cout<<Student::count<<endl;   //静态成员变量可以直接通过类名来访问
    cout<<Student::GetCount1()<<endl;
    cout<<s1.GetCount()<<endl;
    return 0;
}

4.友元函数与友元类

4.1友元函数

在当前类的外部定义、不属于当前类的函数也可以在类中声明,但是要在前面加关键字friend, 这样就构成了友元函数。友元函数可以是不属于任何类的非成员函数,也可以是其他类的成员函数。

友元函数可以访问当前类中的所有成员,包括public、protected、private等属性的成员。      

#include <iostream>
using namespace std;
 
class Test
{
    friend void show(Test &t);  //将show函数声明为Test的友元,show可以访问Test的所有成员变量
    private:
        int m_a;
    public:
        void set(int a)
        {
            m_a = a;
        }
};
 
void show(Test &t)
{
    cout<<t.m_a<<endl;
}
 
int main(int argc, char const *argv[])
{
    Test t1;
    t1.set(2);    
    show(t1);    //通过友元函数可以访问,Test类中的所有成员。
    return 0;
}

4.2友元类

如果将类B声明为类A的友元类,那么类B中的所有成员函数都是类A的友元函数,类B就可以访问类A的所有成员,包括public、protected、private属性成员。

#include <iostream>
using namespace std;
 
class A
{
    friend class B;  //声明B为A的友元,友谊具有单向性,不代表A是B的友元,破坏了他的封装性
    private:
    int m_a;
    public:
        void set(int a)
        {
            m_a = a;
        }
};
 
class B
{
    private:
        int m_b;
    public:
        void print(A &a)
        {
            cout<<"m_a = "<<a.m_a<<endl;
        }
};
 
int main(int argc, char const *argv[])
{
    A a1;
    a1.set(2);
 
    B b1;
    b1.print(a1);   //当B成为A的友元类后,类B的所有成员函数都是A的友元函数,所以通过B的函数
    return 0;       //可以访问A中所有成员。
}


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值