你的位置:首页 > 信息动态 > 新闻中心
信息动态
联系我们

C++基础(C++Primer学习)

2021/12/5 18:40:42

C++基础(五)

C++的类(继续)

上一节的补充:
在创建类对象的时候,如果在类名前加上const关键字来修饰,那么创建的对象其中的数据就是不可被修改的。

const Stock num3("abc", 123, 212);  //创建了一个const型的对象
num3.show(); //报错

对于const型的类对象,编译器无法确保调用成员函数会不会对数据成员进行修改,因此不允许这样写代码,解决的办法就是在声明与定义类成员函数的时候,也使用const关键字进行修饰。

void show() const;  //声明加上const关键字
void Stock::show() const{}  //定义函数时在括号后面加上const关键字

对于C++11及以上的编译器支持使用列表初始化方法来初始化对象:

Stock ob1{param1, param2,param3...};
Stock ob2={param1, param2, param3..};

都是允许的写法。

一、 this指针
参照书上例题,假定设计一个需要比较两个对象总价最大值的函数(total_val),并返回其中较大的一个对象值。
首先在类内部对该函数进行声明:

const Stock& topval(const Stock& s) const;

对于这段代码读起来可能比较难懂,从左开始看:
const Stock& 表示该函数(topval)的返回值类型是一个const型的Stock类的引用。
topval(const Stock &)表示的该函数传入的参数类型必须是Stock类的引用
最后一个const表示,该函数可以调用const类型的数据成员而不会对其数据进行修改。

在类外对该函数进行定义:

const Stock& Stock::topval(const Stock& s) const
{
	if (s.total_val > total_val)
	{
		return s;
	}
	else
	{
		return *this;
	}
}

this指针的用处在这里就体现出来了,对于上面代码返回值的类型,如果返回的是该对象内的值,那么this指针就会指向调用成员的对象。

num1.topval(num2);  //此时的this指针指向调用的num1对象
num2.topval(num1);  //此时的this对象指向调用的num2对象

同样的,在函数括号后面使用const关键字来修饰,就不能够通过this指针来修改对象的值。·
因为this是指针,而我们返回的不是对象的地址而是对象本身,因此这里要加上解引用符号 *。
笔记:
允许代码的时候发现了一个错误就是,调用了show()函数在显示信息的时候,股票总价(total_val)总是显示一个奇怪的数字—9.25596e+061,无论怎么更改也不行,后来明白了我们之前写的代码中,股票总价需要通过一个内联函数计算来完成,而我们在初始化的时候并没有初始化total_val这个变量,这里对构造函数进行修改,将set_total函数在构造函数里面执行一遍就好了。

Stock::Stock(const string &co, long sh, double share_v)
{
	company = co;
	shares = sh;
	share_val = share_v;
	set_total();
}

二、对象数组
实际应用中我们通常需要创建多个对象,因此把对象集中放在一个数组里面可以更加方便进行管理。
创建对象数组时,如果对象的类使用的是默认构造函数,那么只要下面这样定义即可:

 Stock  ob_list[10];

创建了一个对象数组,里面有十个Stock类的对象。
如果我们定义了自己的构造函数,就像Stock类,就需要在创建对象时也完成初始化对象:

Stock list[2] = {
	Stock("dd",1,2),
	Stock("ss",2,3)
	// 创建了两个对象
};

通过下标加.的方式,可以访问对象里面的成员函数:

Stock list[2] = {
	Stock("dd",1,2),
	Stock("ss",2,3),

};
list[1].show();

注:这里访问的是第二个对象,因为第一个对象的下标为0
对于有多个重载构造函数的类,在列表里也可以使用不同的初始化方式。
如果创建的列表对象数多于初始化对象数,那么剩下的对象编译器将使用默认构造函数(在没有自定义构造函数的情况下,否则将报错)
十个对象,仅仅初始化两个:

Stock list[10] = {
	Stock("dd",1,2),
	Stock("ss",2,3),

};

报错:在这里插入图片描述
创建一个const型指针来指向一个数组对象:

const Stock* plist = &list[1];
const Stock& plist1 = list[0];
const Stock* p = &plist->topval(list[0]);

第一行是指针,第二行时引用,第三行时创建了一个新指针来接收topval返回的新对象。
可以看出来指针和引用的区别,引用为该对象创建了另一个名字,叫做plist1,并不是单纯的浅拷贝。

Stock list[2] = {
	Stock("dd",1,2),
	Stock("ss",2,3),

};
const Stock* plist = &list[1];
Stock& plist1 = list[0];
const Stock* p = &plist->topval(list[0]);
//cout << plist;
plist1.show();
list[0].buy(100, 20);
plist1.show();
list[0].show();

通过这段代码可以验证,改变源对象的值,引用的对象值也随之改变,其实也就是两个名字都指向了同一个对象。
关于指针的一点思考:指针前面的类型前缀并不表示指针的类型,而是表示指针指向的对象的类型;类似的,定义函数的类型声明并不表示函数类型,而是表示函数返回值的类型

三、类的作用域
在类中定义的名称的作用域都为整个类,因此该名称只在该类中是已知的,在类外不可知,这样的话不同的类就算使用了相同的的类成员名称也是没有关系的。同样,想要调用类内部的资源,必须通过该类的对象来实现。
教材原话:有时候使符号常量的作用域为类很有用,因此创建一个由所有对象共享的常量是个不错的主意,但是实际上行不通:

class{
privateconst int param = 10;
	.....

原因是类只是描述了对象的形式,并没有创建对象。因此在创建对象之前,没有用于存储值的空间。下面介绍两种实现该功能的方法:
1、使用枚举来完成

class l{
privateenum{Months = 12};
	double costs[Months];
....

用这种方式声明枚举并不会创建类数据成员,所有对象中都不包含枚举。另外,Months只是一个符号名称,在作用域为整个类的代码中遇到他时,编译器将会用数值(在这里是12)来代替它。
2、使用static关键字

class 2{
private:
	static const int Months = 12;
......

这样就可以创建一个名为Months的常量,该常量将与其他静态变量存储在一起,而不是存储在对象里。

四、抽象数据类型
Stock类非常具体,然而实际中常常通过定义类来表示更通用的概念。创建一个stack类来说明这种思想:

#pragma once
#include<iostream>

typedef unsigned long item;  //使用typedef关键字可以提高代码可移植性


class Stack
{
private:
	enum{MAX = 10};
	item items[MAX];
	int top;
public:
	Stack();
	bool isempty() const;
	bool isfull() const;
	bool push(item& item);
	bool pop(item& item);
	void show();
};

头文件,在此基础上我又添加了一个显示函数来显示对象内容。
函数声明写在.cpp文件里:

#include "stack.h"

Stack::Stack()
{
	top=0;
}

bool Stack::isempty()const
{
	return top == 0;  //判断等式是否成立,成立返回时true,不成立返回false。
}

bool Stack::isfull()const
{
	return top == MAX; 
}

bool Stack::push(item& item)
{
	if (top < MAX)
	{
		items[top] = item;
		top++;
		/*l另外一种写法,更加简洁
		items[++top] = item;
		*/
	}
	else
		return false;
}

bool Stack::pop(item& item)
{
	if (top > 0)
	{
		item = items[--top];
	}
	else
		return false;
}

void Stack::show()
{
	std::cout << "the store is:";
	for (int i = 0; i < MAX; i++)
	{
		std::cout<<items[i];	
	}
	std::cout << '\n';
}

看了代码感觉写的还是不够全面,于是在此基础上,我自己改了改又,试了几遍觉得舒服了。把使用类的过程代码也封装成了一个函数。
首先把使用类的代码函数也封装在类成员函数声明的文件里,这样使得main函数体内尽可能地简单,最好只有调用。主要就是解决了一个循环输入问题,剩下还有很多问题.😁

Stack use_stack()
{
	Stack mystack;
	char ch;
	char temp;
	std::cout << "enter A to push to stack\n"
		<< "enter P to pop from stack, Q to quit.\n";
	while (std::cin >> ch && toupper(ch) != 'Q')
	{	
		while (std::cin.get() != '\n')
			continue; //用来处理回车键
		switch (ch)
		{
		case 'A':
			std::cout << "push a word unless Q" << std::endl;
			while (1)
			{
				std::cout << "enter a word:";
				std::cin >> temp;
				if (temp != 'Q')
				{
					if (mystack.isfull())
					{
						std::cout << "stack full" << std::endl;
						break;
					}
					else
						mystack.push(temp);
				}
				else
					break;
			}
			break;
		case 'P':
			if (mystack.isempty())
			{
				std::cout << "satck empty" << std::endl;
			}
			else
				mystack.pop(temp);
			break;
		default:
			continue;
		}

	}
	mystack.show();
	return mystack;
}

这样的话,main函数体内只要一行代码了:

int main()
{
	Stack mystack=use_stack();
......

但是得在main文件内声明一下这个函数,不然用不了。
显示效果图如下:
在这里插入图片描述