C++学习之第四天

一、简答题

1.当定义类时,编译器会为类自动生成哪些函数?这些函数各自都有什么特点?

定义类的时候,编译器自动给类生成默认的默认无参构造函数、拷贝构造函数、析构函数、赋值运算符函数。

解答:

1.构造函数:
    1.名字与类名相同,若程序员在类中自定义了有参构造函数和拷贝构造函数,默认构造函数也得自定义。
    2.实例化对象的时候自动调用,用于初始化成员数据,没有返回值,也没有返回类型。
    3.可以发生构造函数重载。

2.析构函数:
    1.类名前面加~,在对象销毁的时候自动调用,允许被显式调用,显式调用后,对应对象的数据会被清空。
    2.在每个类中有且只有一个,如果类中没有定义,编译器给类提供一个默认的空实现。

3.拷贝构造函数:
    1.编译器会给类提供一个默认的拷贝构造函数,进行简单的值拷贝;
    2.拷贝构造函数被调用的三个时机:1.用已存在的对象去初始化新的对象。
                               2.类的对象作为实参传递给函数,函数形形参接收实参时
                               3.函数的返回值是类的对象时。

4.赋值运算符函数:
    1.对象与对象之间使用赋值运算符,也是简单的浅拷贝,两对象指向同一片内存空间
    
    2.如果发生在堆空间,在进行释放的时候会出现重释放的问题,需要重写该函数进行深拷贝
    
    3.重写赋值运算符函数的步骤:1.自复制    2.释放左操作数     3.进行深拷贝     4.返回*this
    
    4.重写该函数时,返回类型中的&和形参中的&不能去掉的原因:Computer &operator=(const Computer &rhs)
    
         4.1.函数的返回类型是【类】类型,*this是类对象本身,在执行return语句的时候会满足拷贝构造函数调用             时机3,去执行拷贝构造函数,会增加开销。
        
        4.2.形参中的&如果去掉,在实参参数传递给形参的时候,也会去调用一次拷贝构造函数,增加开销
        
        4.3.返回的类型不能改,如果返回的是void类型,赋值函数只支持一次性赋值。

Computer类中重写赋值运算符

Computer &Computer::operator=(const Computer &rhs)
{
    cout << "Computer &operator=(const Computer &)" << endl;
    if(this !=  &rhs)//1、自复制
    {
        delete [] _brand;//2、释放左操作数
        _brand = nullptr;

        _brand = new char[strlen(rhs._brand) + 1]();//3、深拷贝
        strcpy(_brand, rhs._brand);
        _price = rhs._price;
    }

    return *this;//4、返回*this
}

2.什么是左值与右值,拷贝构造函数中的引用与const为什么不能去掉?

1.左值:可以进行取地址对象
右值:不可以取地址的对象,如临时对象/匿名对象,字面常量值都是右值

2.拷贝构造函数中的const和&为什么不能去掉

(1)拷贝构造函数的引用符号不能去掉。
    引用符号去掉之后,拷贝构造函数的实参与形参结合会发生拷贝,则会触发继续调用拷贝函数,从而形成递归,无限调用下去。栈空间大小有限,这样最后会导致stackoverflow。
    使用引用相当于给传进来的变量换个名字,因此不会再调用拷贝构造函数。
    
(2)拷贝构造函数的const关键字不能去掉。
    当传递给函数形参的参数是右值的时候,非const左值引用不能绑定右值。
    const关键字去掉之后,会导致实参与形参结合时,临时对象作为右值不能传递给左值,从而报错。

3.this指针是什么?

1.this指针的本质是一个指针常量。Person *const this 
2.this指针隐藏在非静态成员函数参数的第一个位置,指向对象本身。
3.*this,就是实例化对象本身。以下例子,相当于this =&pNull,那么*this解引用后,就是对象本身pNull.

this指针代码理解

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
class NullPointCall 
{
public:
	NullPointCall(int test):
		_test(test)
	{
		//this指针指向一段内存,内存里存储着pNull对象
		cout << this << endl;//this的地址,和对象pNull的地址一样
		cout << (*this)._test << endl;//输出10
		//*this=pNull,等同在外面调用了pNull._test
	}
private:
	int _test; 
}; 
int main()
{
	int a = 10;
	NullPointCall pNull(a);
	cout << &pNull << endl;//实例化对象pNull的地址和this一样
	system("pause");
	return EXIT_SUCCESS;
}

4.类中的数据成员必须在构造函数列表中初始化的3种情况?

1.常量数据成员-const修饰的变量不允许被修改、赋值,因此必须在列表中初始化

2.引用数据成员-int &ref,引用不能独立存在,在定义的时候必须初始化绑定到某个变量,因此必须在列表中初始化。

3.类对象数据成员-一个类作为另一个类的数据成员,如果不在列表中初始化,会调用其无参构造函数。

5.静态数据成员的初始化在哪里,需要注意什么?

1.用static修饰的数据成员:
(1)静态数据成员保存在全局/静态区,不占用对象的存储空间(如sizeof得到的结果不含静态数据成员大小)。

(2)在类内进行声明,在类外进行初始化,初始化时不包含static关键字

(3)被该类的所有对象所共享,可以通过实例化对象或者类名来访问。

2.用static修饰的成员函数。

(1)没有this指针

(2)只能访问静态数据成员

(3)被所有对象所共享

6.常函数和常对象

1.常函数.void showPerson()const
    1.修饰成员函数中的this指针,让指针指向的内容不允许被修改;
    
    2.本质:this指针的本质为:Person *const this,
           成员函数后加const等同于:const Person *const this;使this所指向的内容也不允许被修改
           
    3.但用mutable定义的数据成员,在常函数内允许被修改
    -即在常成员函数内,不允许修改普通成员,但可以修改用mutable定义的数据成员

2.常对象:
    1.常对象只允许访问常函数和mutable定义的成员数据,不允许访问普通成员。

优势:
    2.非常对象(const实例化的对象)允许访问常函数或者mutable修饰的数据成员。

二、写出下面程序结果。

1、写出以下程序运行的结果。

#include <math.h>
#include <iostream>

using std::endl;
using std::endl;

class Point	
{
public:
    Point(int xx = 0, int yy = 0) 
	{
		X = xx;
		Y = yy;
		cout << "point构造函数被调用" << endl;
	}
	

   	Point(Point &p);
   	
   	int GetX() 
   	{
   		return X;
   	}
   	
   	int GetY() 
   	{
   		return Y;
   	}

private:
	int X,Y;
};

Point::Point(Point &p)	
{
	X = p.X;
	Y = p.Y;
	cout << "X = " << X << " Y= " << Y << " Point拷贝构造函数被调用" << endl;
}

class Distance	
{
public:	
	Distance(Point xp1, Point xp2);
	double GetDis()
	{
		return dist;
	}
private:	
	Point p1,p2;	
	double dist;	
};

Distance::Distance(Point xp1, Point xp2)
: p1(xp1)
,p2(xp2)
{
	cout << "Distance构造函数被调用" << endl;
	double x = double(p1.GetX() - p2.GetX());
	double y = double(p1.GetY() - p2.GetY());
	dist = sqrt(x * x + y * y);
}

int main()
{
	Point myp1(1,1), myp2(4,5);
	Distance myd(myp1, myp2);
	cout << "The distance is:" ;
	cout << myd.GetDis() << endl;
	
	return 0;
}

运行结果

point构造函数被调用    //myp1(1,1)
point构造函数被调用    //myp2(4,5)
X = 4 Y= 5 Point拷贝构造函数被调用  //为什么有四个:1.对象作为实参传递调用拷贝构造函数
X = 1 Y= 1 Point拷贝构造函数被调用    //参数传递从右到左        
X = 1 Y= 1 Point拷贝构造函数被调用    //2.已存在的对象作为新实例化对象的初始值,
X = 4 Y= 5 Point拷贝构造函数被调用
Distance构造函数被调用 //myd(myp1, myp2)
The distance is:5

2、写出以下程序运行的结果。

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
class MyClass
{
public:
	MyClass(int i = 0)
	{
		cout << i;
	}
	MyClass(const MyClass &x)
	{
		cout << 2;
	}
	MyClass &operator=(const MyClass &x)
	{
		cout << 3;
		return *this;
	}
	~MyClass()
	{
		cout << 4;
	}
};
void test01()
{
	MyClass obj1(1), obj2(2);//1,2
	MyClass obj3 = obj1;//2,4,4,4
}
int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}

运行结果

122444

分析

//区别赋值运算符函数的使用和拷贝构造函数的区别

Myclass obj3 = obj1; //实例化对象隐式定义,相当于Myclass obj3(obj1)
 
obj3 = obj1;  /* 这是对象的赋值运算*/

3、不考虑任何编译器优化(如:NRVO),下述代码的第10#会发生

#include <iostream>

using std::cout;
using std::endl;

class B
{
public:
	B()
	{
        cout << "B()" << endl;
    }

    ~B()
    {
    	cout << "~B()" << endl;
    }
    
    B(const B &rhs)
    {
        cout << "B(const B&)" << endl;
    }
    
    B &operator=(const B &rhs)
    {
    	cout << "B &operator=(const B &s)" << endl;
    
        return  *this;
    }
};

B func(const B &rhs)
{
    cout << "B func(const B &)" << endl;
    return rhs;
}


int main(int argc, char **argv)
{
	B b1,b2;
    b2=func(b1);//10#

	return 0;
}

程序运行结果

B()  //B b1 
B()  //B b2
B func(const B &)
B(const B&)  //func(b1) = rhs
B &operator=(const B &s) //b2 = func(b1)
~B() // b2
~B() // func(b1)
~B() //b1

Linux下:g++ hwk3.cc -fno-elide-constructors
才能看到以上结果

三、编程题。

1、实现一个自定义的String类,保证main函数对正确执行

main.cpp

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;
//#include <string>
#include "myString.h"


void test01()
{
	MyString str="abc"; //1.实现有参构造函数,
	//cout << str << endl;
	cout << str << endl;//2.实现打印对象的功能
	
	MyString p2 = str;//3.把已有对象作为初始值,重写拷贝构造函数
	
	cout << p2 << endl; 

	p2 = "hello world"; //4.重写赋值运算符,

	cout << p2 << endl;

	str= p2; //5.重写赋值运算符,进行深拷贝

	cout << str << endl;

}

int main() {
	test01();

	system("pause");
	return EXIT_SUCCESS;
}

myString.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
class MyString
{
	friend ostream &operator<<(ostream &cout, const MyString &p);//
public://1.重写左移运算符,只能写在外面,设置友元,实现直接把对象打印,能打印出其内容
	MyString(){};
	MyString(const char *str1);//2.默认有参构造函数,实现MyString str="abc"或者 str("abc")

	MyString(const MyString &p_str);//拷贝构造函数,实现MyString p1=p2

	MyString &operator=(const char *str1)//重载赋值运算符,实现p1 = "abcdse"
	{
		if (this->str != NULL) 
		{
			delete[]str;
			str = NULL;
		}
		str = new char[strlen(str1) + 1];
		strcpy(str, str1);

		return *this;
	}

	MyString &operator=(const MyString &p_str) //实现深拷贝 p1 = p2
	{
		if (this!= &p_str) //1.自复制
		{
			delete[]str; //2.释放左操作数
			str = NULL;

			str = new char[strlen(p_str.str) + 1];
			strcpy(str, p_str.str); //深拷贝
		}
		

		return *this;//返回自身
	}

	~MyString();
private:
	char *str; //从堆空间申请一个数组来维护字符串
};

//默认有参构造 s1("abc")
MyString::MyString(const char *str1)
{
	str = new char[strlen(str1) + 1];
	strcpy(str, str1);
	//cout << str << endl;
}

//2.拷贝构造 s1(s2)
MyString::MyString(const MyString &p_str)
{
	if (this != NULL)
	{
		delete[] str;
		str = nullptr;

		str = new char[strlen(p_str.str) + 1];
		strcpy(str, p_str.str);
	}
}

//3.cout<<s1直接打印对象能打印出内容
ostream &operator<<(ostream &cout, const MyString &p)
{
	cout << p.str;

	return cout;
}

//析构,释放堆空间
MyString::~MyString()
{
	if (str)
	{
		delete[]str;
		str = nullptr;
	}
} 

2、用C++实现一个单链表

1.一个对象代表一个单链表,实例化一个对象就开启初始化

2.按指定位置插入单链表,实现插入任意类型的数据

3.返回单链表的长度。

4.遍历单链表,提供回调函数打印任意类型数据

5.按指定位置删除链表元素

6.按指定值删除单链表元素,提供对比回调函数

7.清空单链表

main.cpp

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
#include "linkList.h"

struct Person//测试数据
{
	char name[64];
	int age;
};

void myPrint(void *data) //提供打印回调函数,根据不同的类型提供不同的打印函数
{
	struct Person *p = (struct Person *)data;
	cout << "姓名:" << p->name
		<< ",年龄:" << p->age<<endl;
}
bool myStructCompare(void *data1, void *data2)//提供对比回调函数
{
	struct Person *p1 = (struct Person *)data1;
	struct Person *p2= (struct Person *)data2;

	return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}
void test01()//测试1
{

	//创建数据
	struct Person p1 = {"小乔", 18 };
	struct Person p2 = {"周瑜", 20 };
	struct Person p3 = {"大乔", 30 };
	struct Person p4 = {"孙策", 32 };
	struct Person p5 = {"诸葛亮", 36 };

	LList list1;

	list1.insert_LinkList(0, &p1);
	list1.insert_LinkList(1, &p2);
	list1.insert_LinkList(1, &p3);
	list1.insert_LinkList(1, &p4);
	list1.insert_LinkList(1, &p5);

	
	list1.foreach_List(myPrint);//遍历单链表
	cout <<"当前链表长度为:"<< list1.list_Length() << endl;

	list1.remove_By_pos(1);//删除链表中第一个元素

	list1.foreach_List(myPrint);
	cout << "当前链表长度为:" << list1.list_Length() << endl;

	list1.remove_By_Value(&p5, myStructCompare);//删除诸葛亮
	list1.foreach_List(myPrint);
	cout << "当前链表长度为:" << list1.list_Length() << endl;

	list1.clear_List();

	cout << "当前链表长度为:" << list1.list_Length() << endl;

}
void myInPrint(void *data)//回调函数打印整型数据
{
	int *num = (int *)data;
	cout << *num << endl;
}
bool myIntCompare(void *data1, void *data2)//回调函数整数对比
{
	int *num1 = (int *)data1;
	int *num2 = (int *)data2;

	return *num1 == *num2;
}
void test02()
{
	//创建数据
	int p1 = 18;
	int p2 = 20;
	int p3 = 30;
	int p4 = 32;
	int p5 = 36;
	LList list1;

	list1.insert_LinkList(0, &p1);
	list1.insert_LinkList(1, &p2);
	list1.insert_LinkList(1, &p3);
	list1.insert_LinkList(1, &p4);
	list1.insert_LinkList(1, &p5);


	list1.foreach_List(myInPrint);//遍历单链表
	cout << "当前链表长度为:" << list1.list_Length() << endl;

	cout << "删除链表中第一个元素" << endl;
	list1.remove_By_pos(1);//删除链表中第一个元素

	list1.foreach_List(myInPrint);
	cout << "当前链表长度为:" << list1.list_Length() << endl;

	list1.remove_By_Value(&p5, myIntCompare);//删除36
	cout << "删除36" << endl;
	list1.foreach_List(myInPrint);
	cout << "当前链表长度为:" << list1.list_Length() << endl;

	cout << "清空链表" << endl;
	list1.clear_List();

	cout << "当前链表长度为:" << list1.list_Length() << endl;

}
int main()
{

	//test01();
	test02();

	system("pause");
	return EXIT_SUCCESS;
}

linkList.h

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>
struct LinkNode
{
	void *data;
	struct LinkNode *next;
};

class LList
{
public:
	LList();//1.初始化构造函数

	//2.单链表的插入
	void insert_LinkList(int pos, void * data);

	//3.返回链表长度
	int list_Length()
	{
		return length;
	}

	//4.打印链表中的每个元素
	void foreach_List(void(*myForeach)(void *));

	//5.按位序删除单链表中的每个元素
	void remove_By_pos(int pos);


	//6.按指定值来删除单链表中的元素
	void remove_By_Value(void *data, bool(*myCompare)(void *, void *));

	//7.清空单链表
	void clear_List();

	~LList() {}; //析构函数

private:
	struct LinkNode pHeader;
	int length;
};

linkList.cpp

#pragma once
#include "linkList.h"

LList::LList()//1.初始化构造函数
{
	pHeader.next = NULL; //头指针置空
	length = 0; //初始化链表长度
}

//2.单链表的插入:指定位置插入,往第pos位置插入数据
void LList::insert_LinkList(int pos, void * data)
{
	if (data == NULL)
	{
		return; //数据为空
	}

	if (pos<0 || pos> length)
	{
		pos = length+1;//pos不合法就进行尾插
	}

	//找到第pos位置的前驱
	struct LinkNode *pCurrent = &pHeader;
	for (int i = 0; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	//遍历结束后,pCurrent指向第pos位置的前驱
	
	struct LinkNode *newNode =(struct LinkNode *) malloc(sizeof(struct LinkNode));//申请新空间
	newNode->data = data;

	newNode->next = pCurrent->next;//进行尾插
	pCurrent->next = newNode;

	length++;
}

//3.打印链表中的每个元素
void LList::foreach_List(void(*myForeach)(void *))//提供回调函数打印任意类型,桥梁
{
	if (length == 0)//链空
	{
		return;
	}
	struct LinkNode *pCurrent = pHeader.next;//用于遍历当前结点

	for (int i = 1; i <= length; i++)
	{
		myForeach(pCurrent->data);//回调函数
		pCurrent = pCurrent->next;
	}
}

//4.按指定位置来删除单链表中的每个元素
void LList::remove_By_pos(int pos)
{
	if (length == 0)
	{
		cout << "当前链表为空,没得删" << endl;
		return;
	}

	if (pos<1 || pos>length)
	{
		cout << "pos值不正确,删除失败" << endl;
		return;
	}

	//找到pos的前驱结点
	struct LinkNode *pCurrent = &pHeader;
	for (int i = 1; i < pos; i++)
	{
		pCurrent = pCurrent->next;
	}
	struct LinkNode *pDel = pCurrent->next;

	pCurrent->next = pDel->next;

	free(pDel);
	length--;

}

//5.按指定值来删除单链表的元素
void LList::remove_By_Value(void *data, bool(*myCompare)(void *, void *))
{
	if (data == nullptr)
	{
		return;
	}
	if (myCompare == nullptr)//用户不提供回调函数,直接返回
	{
		return;
	}
	if (length == 0)
	{
		cout << "链表为空" << endl;
		return;
	}
	struct LinkNode *pCurrent = pHeader.next;//指向第一个元素
	for (int i = 1; i <= length; i++)
	{
		if (myCompare(pCurrent->data, data))//对比规则根据数据类型的不同来编写
		{
			remove_By_pos(i);//按指定位置删除
			break;
		}
		pCurrent = pCurrent->next;

	}
}

//6.清空单链表
void LList::clear_List()
{
	if (length == 0)
	{
		return;
	}
	while (pHeader.next != nullptr)
	{
		struct LinkNode *pCurrent = pHeader.next;
		pHeader.next = pCurrent->next;	
		free(pCurrent);
	}

	length = 0;
}

3、实现上课时候的单例模式的代码

1.通过一个类,只能实例化唯一的一个对象

2.将构造函数私有化后,类就不能创建多个对象,否则会出错

        -可以通过类的静态变量来初始化唯一一个对象,类内声明,类外初始化,

        -将这个对象变量私有化,通过静态成员函数来提供只读接口

        -将拷贝构造函数私有化,防止间接实例化对象

3.对外提供一个getinstance 接口,将指针返回,
4.应用场景:全局唯一的资源,日志记录器、网页库、字典库

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include<string>

//设计需求:一个类只能创建一个对象
//
//应用场景:全局唯一的资源,日志记录器、网页库、字典库

class Singleton
{
public:
	static Singleton *getInstance()//只能用静态成员函数来访问静成员
	{
		if (_pInstance == nullptr) //外界通过该函数获取该唯一对象时,只有在第一次获取的时候申请堆空间
		{
			_pInstance = new Singleton(); //
		}

		return _pInstance;//已有对象的情况下,多次调用此函数,只会返回唯一的指针
	}

	static void destroy()
	{
		if (_pInstance)//如果堆空间存在,才释放
		{
			delete _pInstance;
			_pInstance = nullptr;
		}
	}


private:
	Singleton()
	{
		cout << "Singleton()" << endl;
	}

	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}
	//类内只声明
	static Singleton * _pInstance; //为了保证申请出来的堆空间唯一,需要一个成员来保存堆的地址
};
Singleton *Singleton::_pInstance = nullptr;//类外定义和初始化

int main()
{

	Singleton *ps1 = Singleton::getInstance();
	Singleton *ps2 = Singleton::getInstance();//再次获取,会获得同样的地址
	cout << "ps1 = " << ps1 << endl;
	cout << "ps2 = " << ps2 << endl;

	Singleton::destroy();//手动释放
	Singleton::destroy();
	Singleton::destroy();
	Singleton::destroy();
	Singleton::destroy();

	/* delete ps1;//error */
	/* delete ps2; */
	return 0;

	system("pause");
	return EXIT_SUCCESS;
}

4、上课的时候,单例模式的代码中,对象是放在堆上的,大家可以看看除了堆还有哪些地方可以存放这个唯一的对象,可以写出这样的代码?

提示:可以看看全局的、静态的、栈上的。

待解答~

四、算法题(选做,如果做不出来可以不做)

1、矩阵中的路径

题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。例如在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用下划线标出)。但矩阵中不包含字符串“abfb”的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。

待完成

2.剪绳子

题目:给你一根长度为n绳子,请把绳子剪成m段(m、n都是整数,n>1并且m≥1)。每段的绳子的长度记为k[0]、k[1]、……、k[m]。k[0]*k[1]*…*k[m]可能的最大乘积是多少?例如当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到最大的乘积18。

待完成

3、二进制中1的个数

题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如:把9表示成二进制是1001,有2位是1。因此如果输入9,该函数输出2。

待完成

4、数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1, 2, 3, 2, 2, 2, 5, 4, 2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。

待完成

猜你喜欢

转载自blog.csdn.net/weixin_49278191/article/details/121044984