一、简答题
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() //b1Linux下: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。
待完成