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);
}