【C++】【剑指offer】

1、sizeof的概念

定义一个空的类型,里面没有任何数据成员和变量,对该类型求sizeof得到的结果是什么?

看具体的编译,空类型占用对少个size

1、1为什么不是0?

我们申明该类型的实例,或者说对象的时候,本身就要占一定内存空间,否则无法使用这些实例,具体占用的字节就是编译器决定的。在vs中空类型占1个字节。

1、2如果在该类型中添加构造函数和析构函数

也是一样

调用构造函数和析构函数只需要知道函数的地址即可,而这些函数的地址只与函数类型相关与函数的实例不相干,编译器也不会为这两个函数添加额外的信息

1.3如果将析构函数定义为虚函数

C++编译器一旦发现一个类型中存在虚函数,就会为该类型生成虚函数表,在实例中添加一个指针指向虚函数,在32位机器上一个指针占用4个字节,在64位上一个指针占用8个字节。

2、复制(拷贝)构造函数传值参数

#include <iostream>
using namespace std;
 
class A
{
private:
	int value;
public:
	A(int n){ value = n; }
	A(A other){ value = other.value; }
	//A(const A& other){ value = other.value; }
 
 
	void Print(){ cout << value << endl; }
};
 
int main()
{
	A a = 10;
	A b = a;
	b.Print();
 
	return 0;
}

2.1程序运行的结果如何?为什么?

结果是程序崩溃,因为A(A other)实际上以形参的形式将参数传进来,就会反复在构造函数内调用构造函数,导致程序崩溃。

要解决这个问题,我们可以把构造函数修改为A(const A& other),也就是把传值参数改成常量引用。

2.2如下拷贝构造函数将被调用多少次?为什么?

另一个关于拷贝构造函数被调用的次数

#include<iostream>
#include<string.h>
#include<stdlib.h>

using namespace std;

class mystring
{
	public:
			mystring(char *data=NULL);
			~mystring();
			mystring(const mystring & str);
			mystring & operator=(const mystring &str);
			void print();
	private:
			char *m_data;
};

mystring::mystring(char *data)
{
	if(data==NULL){
		m_data = new char[1];
		m_data[0] = '\0';
	}else{
		int len = sizeof(data);
		m_data = new char[len];
		strcpy(m_data, data);
	}
	cout << m_data << endl;
}

mystring::~mystring()
{
	// 实际上这个条件是可以不需要的
	if(m_data!=NULL){
		delete [] m_data;
	}
}

void func(mystring str)
{
	mystring temp(str);
}

// 拷贝构造函数
mystring::mystring(const mystring & str)
{
	int len = sizeof(str.m_data);
	m_data = new char[len];
	strcpy(m_data, str.m_data);
	cout << "copy: " ;
	cout << m_data << endl;
}

void mystring::print()
{
	cout << m_data << endl;
}
int main()
{
	char str[] = "yuanhui";
	//char *str = NULL;
	mystring m_str(str);
	
	// 拷贝构造函数会被调用三次
	// 1、m_str1(m_str);  2、m_str的实参传递给形参str 3、mystring temp(str);
	mystring m_str1(m_str);
	func(m_str1);
	
	//m_str.print();
	return 0;
}

3、添加赋值运算符函数

#include<iostream>
#include<stdlib.h>
#include<string.h>

using namespace std;

class Cmystring
{
	public:
		Cmystring(char *data=NULL);
		Cmystring(const Cmystring & str);
		~Cmystring();
		Cmystring & operator=(const Cmystring & str);
	private:
		char *m_data;
	
};

Cmystring::Cmystring(char *data){
	if(data==NULL){
		m_data = new char[1];
		m_data[0]='\0';
	}else{
		int len = strlen(data);
		m_data = new char[len+1];
		strcpy(m_data, data);
	}
	cout << m_data << endl;
}
Cmystring::~Cmystring()
{
	delete[] m_data;
}

// 拷贝构造函数,传入的引用一定不为NULL,因为在他的构造函数里面对他进行了空间分配
Cmystring::Cmystring(const Cmystring &str)
{
	int len = strlen(str.m_data);
	m_data = new char[len+1];
	// 那这个类的m_data岂不是被赋值了两次
	strcpy(m_data, str.m_data);
}



Cmystring & Cmystring::operator=(const Cmystring &str)
{
	if(this != &str){// 防止自己赋值给自己
		// 当空间不足,容易造成异常安全性问题
		/*
		delete[] m_data;
		m_data = NULL;
	
		int len = strlen(str.m_data);
		m_data = new char[len+1];
		strcpy(m_data, str.m_data);
		*/
		Cmystring strtemp(str);
		char  *temp = strtemp.m_data;
		strtemp.m_data = m_data;//方便在析构函数中释放内存
		m_data = temp;
		
	}
	return *this;
}

int main()
{
	char strs[]="hello xuxing";
    Cmystring str1(strs);
    Cmystring str2;
	Cmystring str3, str4;
    str2=str1;
	str1 = str1;
	str3 = str4 = str1;
	
	return 0;
}

C++有六个类的默认函数:构造、析构、拷贝构造、赋值运算符重载、

取地址操作运算符重载和const修饰的去地址操作符重载

对于拷贝构造以及赋值运算符重载,默认使用的是浅拷贝,即将对象内存原封不动的挪动到新对象的内存中,比如直接赋值

对于含有指针的类,往往需要自己实现copy来完成深拷贝,否则很可能多个指针指向同一块内存,多次析构同一块空间导致崩溃。

一个深拷贝的例子:

Cmystring & Cmystring::operator=(const Cmystring &str)
{
	if(this != &str){// 防止自己赋值给自己
		// 当空间不足,容易造成异常安全性问题
		delete[] m_data;
		m_data = NULL;
	
		int len = strlen(str.m_data);
		m_data = new char[len+1];
		strcpy(m_data, str.m_data);
        /*
		Cmystring strtemp(str);
		char  *temp = strtemp.m_data;
		strtemp.m_data = m_data;//方便在析构函数中释放内存
		m_data = temp;
		*/
	}
	return *this;
}

因为之前模拟写过String类的简单实现,所以我想了一下,就写了上面的代码。在这里,几个值得注意的点: 
1. 赋值运算符的重载函数的声明,需要返回类型的引用,也就是CMyString& ,这里是为了考虑到形如 a = b = c这样的连续赋值操作,因此需要在函数结束前加上return *this; 
2. 函数传参需要引用,这样避免了调用一次拷贝构造函数提高效率,同时为了不改变传入实例,需要加上const 
3. 重新分配内存时候,必须要释放之前自己已有的空间,否则会导致内存泄漏 
4. 要考虑到自己赋值给自己,即this == &other时候,其实不需要执行任何操作,同时更为重要的是:对于我自己写的代码如果不加上if (this != &other)代码就是错的,因为我是先释放内存再根据other需要的空间开辟一块新空间,对于自己赋值给自己的情况,由于已经自己指向的那块空间已经释放了,所以再也找不到需要赋值的内容了。

对于我之前写的代码,我是先释放之前的内存再开辟新空间,如果此时内存不足导致new时抛出异常,那么此时m_pData已经为空指针,容易导致程序崩溃,这样违背了异常安全性(Exception Safety)的原则,因此可以采用先分配新空间,分配成功后再释放原来的内容,当然书上给出了一个更好的方法,先创建一个临时实例,再交换临时实例和原来的实例。代码如下:

CMyString& CMyString::operator=(const CMyString& other)
{
    if (this != &other)
    {
        //先分配空间
        char *pTemp = new char[strlen(other.m_pData) + 1];
        strcpy(pTemp, other.m_pData);
        //分配成功后再释放原来的内存
        delete[] m_pData;
        m_pData = nullptr;
        m_pData = pTemp;
    }
    return *this;
}

更好的办法:

CMyString& CMyString::operator=(const CMyString& other)
{
    if (this != &other)
    {
        CMyString temp(other);
        swap(temp.m_pData, m_pData);        
    }
    return *this;
}

在这个函数中,我们创建了一个临时对象temp,然后交换了temp.m_pData和m_pData指向的空间,此时temp指向的空间即为m_pData之前的空间,由于temp是个局部对象,运行到if作用域外,就用自动调用temp的析构函数从而完成了内存的释放,同时也完成了相应的拷贝工作。代码也简洁的多,确实是个更好的办法。当然我觉得,代码可以在此基础上更简便一点:

CMyString& CMyString::operator=(CMyString other)
{
    swap(other.m_pData, m_pData);       
    return *this;
}

这样写需要改变参数的传参,传值而非引用,由于传值时这个参数仅仅只是调用者的一份拷贝(不用考虑自己给自己的情况),相当于上面创建临时对象,此时交换析构原理和上面基本类似不再赘述。

C++实现单例模式

详见:https://blog.csdn.net/feifei_csdn/article/details/81542195

数据结构:有序二维数组中查看数据

#include<stdio.h>
#include<error.h>

#define ERROR	-22

static int findkey(int (*arr)[5], int col, int start_c,int lom, int start_l, int key)
{
	// debug
	#if 1
	int i = 0, j =0;
	for(i=start_c; i<col; i++){
		for(j=0; j<lom; j++){
			printf("%d\t", arr[i][j]);
		}
		printf("\n");
	}
	printf("\n");
	#endif
	
	int ret = 0;
	
	if(arr==NULL){
		fprintf(stderr, "%s arr is NULL\n", __func__);
		return ERROR;
	}
	if(col<0||lom<0||start_c>col||start_l>lom){
		printf("col is %d\t lom is %d\n", col, lom);
		printf("start_c is %d\tstart_l is %d\n", start_c, start_l);
		fprintf(stderr, "%s argv is error\n", __func__);
		return ERROR;
	}
	if(key<arr[0][0]||key>arr[col-1][lom]){
		return 0;
	}
	if(arr[start_c][start_l]<key){
		// 在下方查找
		if(start_c+1<col&&start_l+1>0)
			ret = findkey(arr, col, start_c+1, lom, start_l, key);
	}else if(arr[start_c][start_l]>key){
		// 在左边和下边查找
		if(start_l-1>0)
			ret = findkey(arr, col, start_c, lom-1, start_l-1, key);
	}else{
		ret = 1;
	}
	return ret;
}
int main()
{
	int arr[3][5] = {{1,5,9,20,25},{2,6,11,23,32},{7,10,15,24,34}};
	int key = 10;
	int ret = findkey(arr, 3, 0, 5, 4, key);
	printf("ret is %d\n", ret);
}

调试可以看到结果

1       5       9       20      25
2       6       11      23      32
7       10      15      24      34

1       5       9       20
2       6       11      23
7       10      15      24

1       5       9
2       6       11
7       10      15

2       6       11
7       10      15

2       6
7       10

7       10

ret is 1

字符串

常量字符串,C/C++为了节省空间将常量字符串放在一个单独的空间来存储,当几个指针赋值给相同的常量字符串的时候,他们实际上是指向相同的地址空间。

int main()
{
    char str1[] = "hello world";
    char str2[] = "hello world";

    char* str3 = "hello world";
    char* str4 = "hello world";

    if(str1 == str2)
        printf("str1 and str2 are same.\n");
    else
        printf("str1 and str2 are not same.\n");

    if(str3 == str4)
        printf("str3 and str4 are same.\n");
    else
        printf("str3 and str4 are not same.\n");

    return 0;
}

str1!=str2

str3==str4

str1和str2是两个字符串数组,我们会为它们分配两个长度为12个字节的空间,并把”hello world”的内容复制上去。这是两个初始地址不同的数组,因此str1和str2的值也不相同。 
str3和str4是两个指针,我们无须为它们分配内存以存储字符串的内容,而只需要把它们指向”hello world”在内存中的地址就可以了。由于”hello world”是常量字符串,它在内存中只有一个拷贝,因此str3和str4指向的是同一个地址。

字符串的替换

在URL地址中经常遇到需要将特殊的字符进行转换

#include<stdio.h>
#include<error.h>
#include<string.h>
#define ERROR       -22
using namespace std;

static int changestr(char *str, int len)
{
	if(str==NULL||len <=0){
		fprintf(stderr, "str should not NULL || len should not zero\n");
		return ERROR;
	}
	int change_size = 0;
	char *ptr = NULL;
	ptr = str;
	while(*ptr){
		printf("%c", *ptr);
		if(*ptr==' '){
			change_size++;
		}
		ptr++;
	}
	printf("\nlen is %d\n", len);
	change_size = len + change_size*2;
	char ret_str[change_size+1];
	printf("\nchange_size is %d\n", change_size);
	
	char *pt = ret_str;
	ptr = str;
	while(*ptr){
		if(*ptr==' '){
			*pt = '%';
			*++pt = '2';
			*++pt = '0';
		}else{
			*pt = *ptr;
		}
		pt++;
		ptr++;
	}
	
	printf("ret_str is %s\n", ret_str);
	return 0;
}
int main()
{
	char str[]  = "you are happy";
	changestr(str, strlen(str));
}

拓展:由以前的从前往后转换成从后往前的思想

#include<stdio.h>
#include<error.h>
#include<string.h>
#define ERROR       -22
using namespace std;

static int sortarr(int *arr1, int len1, int *arr2, int len2)
{
	if(arr1==NULL||arr2==NULL){
		fprintf(stderr, "%s:failed to copy arr1\n", __func__);
		return ERROR;
	}
	
	if(len1<=0||len2<=0){
		fprintf(stderr, "%s:len should not be zero\n", __func__);
		return ERROR;
	}
	
	int i = 0, len = len1+len2-1;
	int arr[len];
	int k=len1-1, j=len2-1;
	for(i=len; i>=0; i--){	
		if(k>=0&&j>=0){
			if(arr1[k]>=arr2[j]){
				arr[i] = arr1[k];
				k--;
			}else {
				arr[i] = arr2[j];
				j--;
			}
		}else if(k<0&&j>=0){
			arr[i] = arr2[j];
			j--;
		}else if(j<0&&k>=0){
			arr[i] = arr1[k];
			k--;
		}
	}
	
	for(i=0; i<=len; i++){
		printf("%d\t", arr[i]);
	}
	printf("\n");
}

int main()
{
	int arr1[] = {2,3,4,6};
	int len1 = sizeof(arr1)/sizeof(arr1[0]);
	int arr2[] = {5,8,9,10};
	int len2 = sizeof(arr2)/sizeof(arr2[0]);
	sortarr(arr1, len1, arr2, len2);
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值