【C++】——类和对象

面向过程和面向对象的初步认识

C语言是面向过程的,关注的是过程,分析求解问题的步骤,通过函数调用逐步解决问题
C++是基于面向对象的,关注的是对象,将一件事情拆分成不同的对象,靠对象之间的交互完成
C++不是纯面向对象的

在这里插入图片描述

类的引入

C语言中,结构体内只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数。(C++兼容C语言,结构体的用法可以继续使用,同时struct也升级成了类)
在这里插入图片描述

struct Stack
{
	//成员函数
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}

	//成员变量
	int* a;
	int top;
	int capacity;
};
void StackInit(struct Stack* ps)
{
	//....
}
int main()
{
	struct Stack st1;
	StackInit(&st1);


	Stack st2;
	st2.Init();
	return 0;
}

类的定义

class className
{
	//类体:由成员函数和成员变量组成

};//注意分号

struct和class最主要的区别就是访问限定符

1、类可以做声明和定义分离:
Test.cpp:
在这里插入图片描述
Func.cpp:
在这里插入图片描述
Func.h:
在这里插入图片描述
2、类的两种定义方式:

1、声明和定义全部放在类体中,需要注意:成员函数如果在类中定义,编译器可以能会将其当成内联函数处理。函数不进入符号表。
2、类的声明放在.h文件当中,成员函数定义放在.cpp文件当中,注意:成员函数之前需要加类名。

类的访问限定符及封装

1、访问限定符:public protected private

C++实现封装的方式:用类将对象的属性和方法结合在一起,让对象更加完善,荣光访问权限选择性的将其接口提供给外部的用户使用。

1、public 修饰的成员在类外可以直接被访问
2、protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
3、访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现为止
4、如果后面没有访问限定符,作用域到 } 结束
5、class的默认访问权限是private,struct为public(因为struct要兼容C)

#include <iostream>
using namespace std;
class Stack
{
public :
	//成员函数
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}
	void push(int x)
	{
		if (top == capacity)
		{
			int newcapacity = capacity == 0 ? 4 : 2 * capacity;
			a = (int*)realloc(a, sizeof(int) * newcapacity);
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	int Top()
	{
		return a[top - 1];
	}
private:
	//成员变量


	int* a;
	int top;
	int capacity;
};
int main()
{
	Stack st;
	st.Init();
	st.push(1);
	st.push(2);
	st.push(3);
	cout << st.Top() << endl;
	return 0;
}

2、C++中struct和class的区别是什么?

C++需要兼容C语言,所以C++中struct可以当成结构体使用,另外C++中struct还可以用来定义类。和class定义类是一样的。区别是:struct定义类的默认访问权限是public,class定义的类默认访问权限是private。

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员时,需要使用 :: 作用域操作符指明成员属于哪个类域

类的实例化

1、 用类类型创建对象的过程,称为类的实例化
2、类是对对象进行描述的,是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它;比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

在这里插入图片描述

3、 一个类可以实例化出多个对象,类实例化出的对象占用实际的物理空间,类不占用实际的物理空间,存储类成员量。

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 d;//实例化,开实际的物理空间
	d.Init(2023, 7, 21);
	return 0;
}

类在内存当中不占内存的空间,占文件系统的空间
类不能存储数据,类实例化的对象存储数据

在这里插入图片描述
对象存储方式:
在这里插入图片描述
在这里插入图片描述
计算类的大小:

类中既有成员变量,又有成员函数

class A1 {
public:
	void f1() {}      //4
private:
	int _a;
};

类中仅有成员函数

class A2 {
public:
	void f2() {}        //1
};

类中什么都没有—空类

class A3       //1
{};

分配1字节,不存储数据,只是占位,表示对象存在过

结构体内存对齐规则:

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

this指针

class Date
{
public:
	void Init(int year, int month, int day)
	{
		cout << this << endl;
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << this << endl;
		cout << _year << "-" << _month << "-" << _day << endl;
	} 
private:
	int _year; 
	int _month;
	int _day;
};
int main()
{
	Date d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

此段代码中还隐含了this指针,在编译器中,编译器会默认给代码加上隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

在这里插入图片描述

上述程序代码中,两个成员函数的指针并不是同一个,更准确的来说,是看this指针指向哪个对象,如何指向的是d1对象,那么调用两个成员函数的地址就是相同的。如果同时调用两个对象所指向的成员函数,那么this指针的地址就是不同的。
this指针指向当前调用的对象,是临时变量,函数调用结束后就销毁了

练习题1:

class A
{
public:
void Print()
{
cout << "Print()" << endl;
}
private:
int _a;
};
int main()
{
A* p = nullptr;
p->Print();
return 0;
}

运行成功,打印Print( ),该函数存放在公共代码区,不存到成员变量中,所以p指针不解引用,运行成功

练习题2:

class A
{
public:
  void PrintA()
 {
    cout<<_a<<endl;
 }
private:
int _a;
};
int main()
{
  A* p = nullptr;
  p->PrintA();
  return 0;
}

运行崩溃,此处有空指针的解引用,因为函数存放在公共代码区,p指针不解引用是空,同时this指针也是空,导致程序运行崩溃

this指针存放在哪?

this指针是一个形参,一般存在栈栈帧中。VS下面一般会用ecx寄存器直接传递

this指针的特性

  1. this指针的类型:类类型* const,即成员函数中,不能给this指针赋值。
  2. 只能在“成员函数”的内部使用
  3. this指针本质上是“成员函数”的形参,当对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

类的6个默认成员函数

如果一个类当中什么都没有,那么这个类就是空类,但是空类在什么都不写的情况下会自动生成以下6个默认成员函数。
默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
默认构造函数的特点:不传参就可以调用的构造就是默认构造

class classmate
{

};

在这里插入图片描述

构造函数

特性:构造函数是特殊的成员函数,构造函数虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象,而是初始化对象

特性:
1、函数名与类名相同
2、无返回值 (也不需要写void)
3、对象实例化时编译器自动调用对应的构造函数
4、构造函数可以重载 (可以写多个构造函数,提供多种初始化方式)

class Date
{
public:
	Date()//无参构造函数
	{
		_year = 1;
		_month = 1;
		_day = 1;
	}
	Date(int year, int month, int day)//有参构造函数
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

上述构造函数也可以合并写成参数全缺省形式

class Date
{
public:
	//Date()//无参构造函数
	//{
	//	_year = 1;
	//	_month = 1;
	//	_day = 1;
	//}
	//Date(int year, int month, int day)//有参构造函数
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1;
	d1.Print();
	Date d2(2023,7,28);
	d2.Print();
	Date d3(2023, 7);
	d3.Print();
	/*d1.Init(2022, 7, 5);
	d1.Print();
	Date d2;
	d2.Init(2022, 7, 6);
	d2.Print();*/
	return 0;
}

C++实现栈:

#include <assert.h>

class Stack
{
public:
	/*Stack()
	{
		a = nullptr;
		top = capacity = 0;
	}*/

	Stack(size_t n = 4)
	{
		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if(a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			top = 0;
			capacity = n;
		}
	}

	// 成员函数
	//void Init()
	//{
	//	a = nullptr;
	//	top = capacity = 0;
	//}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t 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 << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}

			a = tmp;
			capacity = newcapacity;
		}

		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}
private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};

int main()
{
	Stack st1;
	st1.Push(1);
	st1.Push(2);
	st1.Push(3);
	st1.Push(4);
	
	while (!st1.Empty())
	{
		cout << st1.Top() << " ";
		st1.Pop();
	}
	cout << endl;

	st1.Destroy();

	//Stack st2(1000);
	Stack st2;
	for (size_t i = 0; i < 1000; i++)
	{
		st2.Push(i);
	}

	while (!st2.Empty())
	{
		cout << st2.Top() << " ";
		st2.Pop();
	}
	cout << endl;

	st2.Destroy();
}

推荐在一个类当中使用全缺省构造函数
构造函数,也是默认成员函数,我们不写,编译器会自动生成
编译生成的构造函数的特点:
1、我们不写才会自动生成,我们写了就不会自动生成了
2、内置类型(基本类型 int double)成员不会处理,(C++11,声明支持给缺省值)
3、对于自定义类型的成员才会处理,会去调用这个成员的默认构造函数
如果类中没有显示定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成。
无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个(多个并存会存在调用的二义性)。
注意:无参构造函数,全缺省构造函数,我们没有写编译器默认生成的构造函数,都可以认为时默认构造函数(不传参就可以调用的)。
一般情况下都需要我们自己写构造函数,决定初始化方式

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
int main()
{
	//MyQueue mq;
	Time t;
	return 0;
}

举例:

class A
{
public:
	A(int a)
	{
		cout << "A(int a)" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;

	A _aa;

};
int main()
{
	Date d1;
	//Date d2(2023, 7, 29);
	return 0;
}

运行报错:
在这里插入图片描述

错误原因:类A中不存在默认构造函数,默认构造函数:无参的构造函数和全缺省的构造函数。

代码更正:将类A中的构造函数写成参数全缺省形式

class A
{
public:
	A(int a=1)
	{
		cout << "A(int a)" << endl;
	}
private:
	int _a;
};
class Date
{
public:
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;

	A _aa;

};
int main()
{
	Date d1;
	//Date d2(2023, 7, 29);
	return 0;
}

析构函数

析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。

特性:
1、析构函数名是在类名前加上字符“~”
2、无参数无返回值类型
3、一个类只能有一个析构函数,若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4、对象生命周期结束时,C++编译系统自动调用析构函数
默认的析构函数:
对内置类型成员不做处理
自定义类型的成员会去调用他的析构函数

class Time
{
public:
	Time()
	{
		cout << "Time()" << endl;
		_hour = 0;
		_minute = 0;
		_second = 0;
	}
	~Time()
	{
		cout << "~Time()" << endl;
	}
private:
	int _hour;
	int _minute;
	int _second;
};
int main()
{
	
	Time t;
	return 0;
}

栈需要显式的写析构函数

class Stack
{
public:
	//构造函数
	Stack(size_t n = 4)//unsigned int
	{
		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;
		}
	}
	int Top()
	{
		return a[top - 1];
	}
	void Pop()
	{
		assert(top > 0);
		top--;
	}
	void Push(int x)
	{
		if (top == capacity)
		{
			size_t 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 << capacity << "原地" << endl;
			}
			else
			{
				cout << capacity << "异地" << endl;
			}
			a = tmp;
			capacity = newcapacity;
		}
		a[top++] = x;
	}
	~Stack()
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = 0;
		capacity = 0;

	}
	bool Empty()
	{
		return top == 0;
	}
private:
	int* a;
	int top;
	int capacity;
};
int main()
{
	Stack st1(1000);
	st1.Push(1);
	st1.Push(2);
	while (!st1.Empty())
	{
		cout << st1.Top() << endl;
		st1.Pop();
	}
	return 0;
}

在这里插入图片描述

默认析构函数:
1、内置类型成员不会处理
2、自定义类型成员会调用这个成员的析构函数

C++实现括号匹配

#include <iostream>
#include <assert.h>
using namespace std;

class Stack
{
public:
	Stack(size_t n = 4)//构造函数
	{
		cout << "Stack(size_t n = 4)" << endl;

		if (n == 0)
		{
			a = nullptr;
			top = capacity = 0;
		}
		else
		{
			a = (int*)malloc(sizeof(int) * n);
			if (a == nullptr)
			{
				perror("realloc fail");
				exit(-1);
			}

			top = 0;
			capacity = n;
		}
	}

	~Stack()//析构函数
	{
		cout << "~Stack()" << endl;
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	void Push(int x)
	{
		if (top == capacity)
		{
			size_t 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 << capacity << "原地扩容" << endl;
			}
			else
			{
				cout << capacity << "异地扩容" << endl;
			}

			a = tmp;
			capacity = newcapacity;
		}

		a[top++] = x;
	}

	int Top()
	{
		return a[top - 1];
	}

	void Pop()
	{
		assert(top > 0);
		--top;
	}

	void Destroy()
	{
		free(a);
		a = nullptr;
		top = capacity = 0;
	}

	bool Empty()
	{
		return top == 0;
	}
private:
	// 成员变量
	int* a;
	int top;
	int capacity;
};
bool isValid(const char* s) {
	Stack st;
	while (*s)
	{
		if (*s == '[' || *s == '(' || *s == '{')
		{
			st.Push(*s);
			++s;
		}
		else
		{
			// 不匹配
			if (st.Empty())
				return false;

			char top = st.Top();
			st.Pop();

			// 不匹配
			if ((*s == ']' && top != '[')
				|| (*s == ')' && top != '(')
				|| (*s == '}' && top == '{'))
			{
				return false;
			}

			++s;
		}
	}

	return st.Empty();
}

int main()
{
	// 后定义,先析构
	

	Stack st1;
	Stack st2;

	cout << isValid("[[]]()()") << endl;
	cout << isValid("[[]]]") << endl;


	return 0;
}
<think>嗯,用户想了解Excel中的VLOOKUP函数的用法和参数含义。首先,我需要回忆一下VLOOKUP的基本结构。VLOOKUP是垂直查找函数,用于在表格的首列查找指定的值,然后返回该行中指定列的数据。它的四个参数分别是查找值、表格范围、列索引号和匹配模式。 接下来,我得确认每个参数的具体作用。第一个参数是lookup_value,也就是用户要查找的值,比如产品ID或者姓名。第二个参数是table_array,即查找的范围,这里要注意的是,查找值必须位于这个范围的第一列,否则函数无法正确工作。第三个参数是col_index_num,也就是用户想要返回的数据在表格范围中的第几列,这个列数是从表格范围的第一列开始算起的,不是整个工作表的列。第四个参数是range_lookup,决定是精确匹配还是近似匹配,通常用FALSE或0来指定精确匹配,避免错误。 然后,用户可能需要一个具体的例子来理解如何使用。比如,假设有一个产品价格表,用户想根据产品ID查找对应的价格。这时候,VLOOKUP就可以在ID列查找,然后返回价格列的数据。同时,要提醒用户注意表格范围最好使用绝对引用,比如$A$2:$C$100,这样在拖动公式时范围不会变化。 可能遇到的问题也需要考虑。比如,如果查找值不在表格的第一列,或者列索引号超过了表格的列数,函数会返回错误。此外,数据格式不一致也可能导致问题,比如查找值是数字,而表格中是文本格式的数字,这时候需要统一格式。还有,当出现#N/A错误时,可能是查找值不存在,或者匹配模式设置不正确,需要检查参数是否正确。 用户可能还会问到如何避免常见的错误,比如部分数据匹配出错的情况。这时候需要检查是否有多余的空格,或者数据格式不一致,或者表格范围没有锁定导致拖动时偏移。此外,XLOOKUP作为VLOOKUP的升级版,可能在某些情况下更方便,比如可以向左查找或者返回多列数据,这也是可以提及的内容。 最后,整理步骤,从基础语法到参数说明,再到具体案例,逐步引导用户理解如何使用VLOOKUP函数。同时,提供常见错误和解决方法,帮助用户在实际应用中避免问题。如果有必要,还可以比较VLOOKUP和其他查找函数的区别,比如HLOOKUP或XLOOKUP,但根据用户的问题,主要集中在VLOOKUP本身即可。</think>### Excel VLOOKUP函数详解 #### 一、基础语法与参数说明 VLOOKUP函数的语法为: $$=VLOOKUP(lookup\_value, table\_array, col\_index\_num, [range\_lookup])$$ 包含4个参数: 1. **lookup_value**(必填):要查找的值(如单元格引用或具体值) 2. **table_array**(必填):包含数据的表格范围(必须包含查找列和返回列) 3. **col_index_num**(必填):返回值所在列的序号(从table_array第一列开始计数) 4. **range_lookup**(可选):匹配类型 - `TRUE`/`1`:近似匹配(默认值,需数据升序排列) - `FALSE`/`0`:精确匹配(常用选项) [^1][^2] #### 二、使用步骤演示(工资表查询案例) 假设需要根据员工编号查询工资: 1. 建立查询单元格(如`B12`) 2. 输入公式: ```excel =VLOOKUP(A12, $A$2:$D$100, 4, 0) ``` - `A12`:待查询的员工编号 - `$A$2:$D$100`:锁定数据区域(绝对引用) - `4`:返回第4列(工资列) - `0`:精确匹配 [^2][^3] #### 三、常见错误与解决方法 | 错误现象 | 原因 | 解决方案 | |---------|------|---------| | #N/A | 查找值不存在 | 检查数据源或改用`IFERROR`容错 | | #REF! | 列序号超出范围 | 确认col_index_num ≤ 表格列数 | | 部分匹配失败 | 数据格式不一致 | 统一数值/文本格式 | | 结果错位 | 表格未锁定 | 使用`$`符号固定区域引用 | [^3][^4] #### 四、进阶技巧 1. **多条件查询**: 使用辅助列合并多个条件字段 ```excel =VLOOKUP(A2&B2, $D$2:$F$100, 3, 0) ``` 2. **通配符匹配**: `"*"`匹配任意字符,`"?"`匹配单个字符 ```excel =VLOOKUP("张*", $A$2:$C$100, 3, 0) ``` 3. **跨表查询**: 引用其他工作表数据 ```excel =VLOOKUP(A2, Sheet2!$A$2:$D$100, 4, 0) ``` [^1][^4]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Mikk-

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

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

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

打赏作者

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

抵扣说明:

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

余额充值