类和对象(1)

1.面向过程和面向对象初步认识

C++面向对象但不纯面向对象。JAVA纯面向对象。
所以C++支持C和C++面向对象混编。
C面向过程,关注的是过程

2.类的引入

类和结构体的区别:

  1. 类里可以有数据:成员变量
  2. 类可以定义函数
  3. 可以写成ListNode,不需加struct。
    C struct ListNode是类型。
    C
//C++兼容C结构体的用法。
typedef struct ListNode
{
  int val;
  struct ListNode* next;
}LTN;

C++

//C++把结构体升级成类
struct ListNode//声明这是个类,struct关键字不能去掉
{
  int val;
  ListNode* next;//后面想用这个类型的时候可以去掉。
};

如何用类?
C数据和方法分离,C++方法可以在类的里面

struct Stack
{
//成员函数
//成员函数直接定义到类里面
	void Init(int n=4)//缺省值
	{
		a = (int*)malloc(sizeof(int) * n);
		if (nullptr == a)
		{
			perror("malloc申请空间失败");
			return;
		}
		capacity = n;
		size = 0;
	}
	void Push(int x)
	{
		//...
		a[size++] = x;
	}
	//成员变量
	int* a;
	int size;
	int capacity;
};
int main()
{
	Stack st;//对象
	//如何调用函数?
	st.Init(4);
	st.Push(1);
	st.Push(2);
	st.Push(3);
	st.Push(4);
	return 0;
}

C++可以用struct定义类,但更喜欢用class定义类。

3.类的定义

class className
{
  //类体:包括成员函数(类的方法)和成员变量(类的属性)
}//一定要注意后面的分号!!!!
  • class:定义类的关键字
  • className:类名
  • {}:类的主体
  • 类体中的内容:类的成员
    定义一个声明和定义分离的类。
    缺省参数生命和定义不能同时给,一般在声明给。
    Stack.h
#pragma once
#include <string.h>
#include <stdlib.h>
//类成员函数声明和定义分离
struct Stack
{
	//成员函数
	void Init(int n = 4);//缺省参数在声明给

	void Push(int x);//类的声明
	//成员变量
	int* a;
	int size;
	int capacity;
};

Stack.cpp

#include "Stack.h"
void Stack::Init(int n)//Stack告诉编译器Init不是全局函数,是栈这个类的成员函数
{
	a = (int*)malloc(sizeof(int) * n);//看栈这个类里有没有a这个成员变量
	if (nullptr == a)
	{
		perror("malloc申请空间失败");
		return;
	}
	capacity = n;
	size = 0;
}
void Stack::Push(int x)
{
	//...
	a[size++] = x;
}

用class Stack却编不过,为什么呢?
此时要用到访问限定符。

4.类的访问限定符和封装

4.1访问限定符

C++访问限定符有三种:公有保护和私有,现阶段保护和私有没有区别。共有及在类外可以直接访问。保护和私有在类里可以访问,类外不可访问。

公有保护私有
publicprotectprivate
  1. 访问限定符不会限定在类里面的访问,锁外面的人不锁里面的人。
  2. 类里面可以有多个访问限定符,限定从该访问限定符到下一个访问限定符出现时位置,如果没有下一个访问限定符则到}结束。
  3. class的默认访问限定符是私有,struct是公有(因为struct要兼容C)。所以上一文中报错了。
    实际生活中不希望默认,希望大家指清楚到底是私有还是公有。
    注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别。
#pragma once
#include <string.h>
#include <stdlib.h>
struct Stack
{
publicvoid Init(int n = 4);
	void Push(int x);
privateint* a;
	int size;
	int capacity;
};

大多数情况下成员变量都是私有的,成员函数不给别人用的是私有,给别人用的是公有。
C++中struct和class没有区别,但是struct可以像C语言去用。
Test.cpp

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;

};
int main()
{
	Stack st;

	Date d1;
	d1.Init(2023, 2, 3);//调用函数,年月日被初始化
	return 0;
}

4.2封装

【面试题】:面向对象的三大特性:封装,继承,多态
封装:将下面的细节藏起来,本质是一种更好的管理。
C++的封装:将数据和方法都放在类里面去了,即当前封装的极限,并把自己想访问的定义成共有,不想的定义成私有和保护。

5.类的作用域

类定义出一个新的作用域(类域)。

class Person
{
 public:
   void PrintPersonInfo();
 private:
   char _name[20];
   char _gender[3];
   int _age;
};
void Person::PrintPersonInfo()//PrintPersonInfo属于Person这个类域
{
  cout<<_name<<" "<<_gender<<" "<<_age<<endl;//此时可以调用私有
}

6.类的实例化

即用类类型
声明即要定义这个函数或变量,这个变量的类型是什么,名称是什么,参数是什么,但实际这个变量没有出来。定义即把其实实在在在的空间给开出来(对于变量而言)。
类就像一个别墅的设计图,设计了细节,但不能住人,实例化即根据设计图建造出一栋栋别墅。
在这里插入图片描述

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}//函数的定义
private:
	int _year;
	int _month;
	int _day;//声明,没有开空间

};

只需要计算成员变量的大小。

int main()
{

	//类对象实例化--开空间
	Date d1;//定义才是开空间,对于对象整体定义。
	//Date._year=1;
	//Date::year=0;
	//以上两种不可以,声明内不可存数据。
	//d1._year=1;//也不行,访问限定符是私有的。访问不了,访问的时候需要调用这个函数。改成公有就可以了。
	d1.Init(2023,9,12);//Init函数存在哪里呢?
	d1._year++;
	cout<<sizeof(d1)<<endl;
	//类对象大小要考虑内存对齐规则
	return 0;
}//输出12

为什么成员变量存在对象里面,成员函数不存在对象里面?
每个对象的成员变量不一样,需要独立存储。
每个对象调用的成员函数一样,放到共享公共区域(代码段)。
只需要计算成员变量的大小。

//类中既有成员变量,又有成员函数
class A1{
pubic:
 void f1(){}
private:
 int a;
};//sizeof(A1):4
//类中仅有成员函数
class A2{
public:
 void f2(){}
};//sizeof(A2):1//【考点】如果是0,A2 aa1;没有实例化,不能取地址
//类中什么也没有
class A3
{}//sizeof(A3):1,这一个字节用来进行占位,不存储有效数据,标识对象存在过,被实例化定义出来了。

没有成员变量的类都是一个字节

6.2结构体内存对齐规则

  1. 第一个成员在于结构体偏移量为0的地址处
  2. 其他成员变量要对齐到某个数字(对其书)的整倍的地址处
    注意:对齐数=编译器默认的一个对齐数与该成员大小的较小值。VS默认对齐数为8.
  3. 结构体的总大小:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

7.this指针

原来代码:

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}

编译器处理完:

class Date
{
public:
		void Init(Date*this,int year, int month, int day)
	{
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}

原来代码:

int main()
{
	Date d1;
	Date d2;
	d1.Init(2023, 2, 3);
	d2.Init(2022, 2, 3);

	return 0;
}

编译器处理完:

int main()
{
	Date d1;
	Date d2;
	d1.Init(&d1,2023, 2, 3);
	d2.Init(&d2,2022, 2, 3);

	return 0;
}

如果是d1调用,this是d1的地址,赋值给d1的年月日。
不可以自己去加。

//可以在类里使用this,但是实参和形参里不可以
cout<<this<<endl;
this->_year = year;
this->_month = month;
this->_day = day;
//一般不会这么写
  1. this存在哪里?–存在对象里面?答案❌。
    在栈上,因为他是形参,隐含的形参,不需要显示写,是编译器自己加的。/VS下通过ecx寄存器。
    程序进行编译,编译后成员变量存在对象里,实例化出一个对象,对象存在上。成员函数不要存在栈中,因为成员函数存在一个公共区域,编译的时候要确定call这个函数的地址,这个地址不在对象中去找,在代码段。因为函数的地址是这些指令的地址,这些指令是存在哪呢?存在于代码段。两个东西完全不一样,要从两个不同的角度理解。一个是指令,一个是指令运行过程中的相关数据。不要把两个东西混在一起。
    《深入理解计算机系统》——修炼内功

7.2this指针的特性

void func()
{
 cout<<this<<endl;
 cou<<"func()"<<endl;
 }
int main()
{
//编译报错 运行崩溃 正常运行
  Date* ptr=nullptr;
  ptr->func();
  //结果:正常运行
  ptr->Init(2023,9,12);//运行崩溃,用this去解引用了this->year
  ptr->_year;//会崩溃,因为_year在对象里面,到指针指向的对象去找,指针是一个空指针,相当于解引用。*ptr).func();//正常运行,ptr真正的意义是传递给this,所以也是正常运行,没有解引用这个行为
  Date::func();//不能这么调用,因为要传递this指针。没有this指针调用。
}

指针调用用箭头,有箭头不一定解引用。函数不在对象里。调用这个函数要call这个地址,这个地址在公共区域去找,公共区域:代码段。
成员函数不可以不用对象去调,直接func()。

  1. 受到类域的限制,一般都不在类域里面去找,只在全局去找。在全局找func找不到
  2. 告诉func是Date的成员函数,调用成员函数要传递this指针,所以没有解引用,但是ptr传递给了this指针(cout<<this<<endl;),所以this指针是一个空,但不会报错,只有发生了恶劣的行为才会报错。
  3. 会不会解引用,取决于要不要在对象中去找,而不是有没有这个符号。
    调用函数一共就需要两个动作,一个是传参,传this指针,一个是调用函数,这两个动作都不涉及要去解引用。
    成员函数不是成员对象。

封装(补充)

C语言和C++真正的区别:数据结构的实现与语言无关

CC++
数据和方法分离的都封装在类里面
名字繁琐简洁
数据访问自由不受限制控制访问方式。愿意给你访问的公有,不愿意私有
底层一样

封装在一起才能通过访问限定符限制。

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

豚豚糯

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值