在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
我们知道,假如类的成员函数不会改变对象的状态,那么这个成员函数一般会声明为const。但是,有些时候,我们需要在const的函数里面修改一些跟类状态无关的数据成员,那么这个数据成员就应该被mutalbe来修饰。
1、静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。
#include
using namespace std;
class Myclass
{
private:
int a , b , c;
static int sum; //声明静态数据成员
public:
Myclass(int a , int b , int c);
void GetSum();
};
int Myclass::sum = 0; //定义并初始化静态数据成员
Myclass::Myclass(int a , int b , int c)
{
this->a = a;
this->b = b;
this->c = c;
sum += a+b+c;
}
void Myclass::GetSum()
{
cout<<"sum="<
可以看出,静态数据成员有以下特点:
类成员初始化方式?
这两种方式的主要区别在于:
对于在函数体中初始化,是在所有的数据成员被分配内存空间后才进行的。
列表初始化是给数据成员分配内存空间时就进行初始化,就是说分配一个数据成员只要冒号后有此数据成员的赋值表达式(此表达式必须是括号赋值表达式),那么分配了内存空间后在进入函数体之前给数据成员赋值,就是说初始化这个数据成员此时函数体还未执行。
必须使用成员初始化的四种情况?
C++ 默认构造函数是对类中的参数提供默认值的构造函数,一般情况下,是一个没有参数值的空函数,也可以提供一些的默认值的构造函数,如果用户没有定义构造函数,那么编译器会给类提供一个默认的构造函数,但是只要用户自定义了任意一个构造函数,那么编译器就不会提供默认的构造函数,这种情况下,容易编译报错,所以正确的写法就是用户在定义构造函数的时候,也需要添加一个默认的构造函数,这样就不会造成编译报错。
#include
using namespace std;
class Point{
public:
Point():x(0), y(0){ //默认构造函数
cout<<"Default Constructor called."<
一个派生类构造函数的执行顺序如下:
什么情况会自动生成默认构造函数?
何时需要合成构造函数
构造函数和析构函数调用时机?
全局范围中的对象:构造函数在所有函数调用之前执行,在主函数执行完调用析构函数。
局部自动对象:建立对象时调用构造函数,离开作用域时调用析构函数。
动态分配的对象:建立对象时调用构造函数,调用释放时调用析构函数。
静态局部变量对象:建立时调用一次构造函数,主函数结束时调用析构函数。
10.lambda函数
1)利用lambda表达式可以编写内嵌的匿名函数,用以替换独立函数或者函数对象;
2)每当你定义一个lambda表达式后,编译器会自动生成一个匿名类(这个类当然重载了()运算符),我们称为闭包类型(closure type)。那么在运行时,这个lambda表达式就会返回一个匿名的闭包实例,其实一个右值。所以,我们上面的lambda表达式的结果就是一个个闭包。闭包的一个强大之处是其可以通过传值或者引用的方式捕捉其封装作用域内的变量,前面的方括号就是用来定义捕捉模式以及变量,我们又将其称为lambda捕捉块。
3)lambda表达式的语法定义如下:
[capture] (parameters) mutable ->return-type {statement};
4)lambda必须使用尾置返回来指定返回类型,可以忽略参数列表和返回值,但必须永远包含捕获列表和函数体;
this指针是什么?
this指针是类的指针,指向对象的首地址。
this指针只能在成员函数中使用,在全局函数、静态成员函数中都不能用this。
this指针只有在成员函数中才有定义,且存储位置会因编译器不同有不同存储位置。
类模板是什么?
用于解决多个功能相同、数据类型不同的类需要重复定义的问题。
在建立类时候使用template及任意类型标识符T,之后在建立类对象时,会指定实际的类型,这样才会是一个实际的对象。
类模板是对一批仅数据成员类型不同的类的抽象,只要为这一批类创建一个类模板,即给出一套程序代码,就可以用来生成具体的类。
11.cout和printf有什么区别?
cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。
cout是有缓冲输出:
cout < < "abc " <
一、什么是拷贝构造函数
首先对于普通类型的对象来说,它们之间的复制是很简单的,例如:
int a=100;
int b=a;
而类对象与普通对象不同,类对象内部结构一般较为复杂,存在各种成员变量。
下面看一个类对象拷贝的简单例子。
复制代码
复制代码
#include
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a=b;
printf("constructor is called\n");
}
//拷贝构造函数
CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
}
//析构函数
~CExample()
{
cout<<"destructor is called\n";
}
void Show()
{
cout<
using namespace std;
class CExample
{
private:
int a;
public:
CExample(int b)
{
a=b;
printf("constructor is called\n");
}
CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
}
~CExample()
{
cout<<"destructor is called\n";
}
void Show()
{
cout<
using namespace std;
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a=b;
printf("constructor is called\n");
}
//拷贝构造函数
CExample(const CExample & c)
{
a=c.a;
printf("copy constructor is called\n");
}
//析构函数
~CExample()
{
cout<<"destructor is called\n";
}
void Show()
{
cout<
using namespace std;
class Rect
{
public:
Rect()
{
count++;
}
~Rect()
{
count--;
}
static int getCount()
{
return count;
}
private:
int width;
int height;
static int count;
};
int Rect::count=0;
int main()
{
Rect rect1;
cout<<"The count of Rect:"<
using namespace std;
class Rect
{
public:
Rect()
{
count++;
}
Rect(const Rect& r)
{
width=r.width;
height=r.height;
count++;
}
~Rect()
{
count--;
}
static int getCount()
{
return count;
}
private:
int width;
int height;
static int count;
};
int Rect::count=0;
int main()
{
Rect rect1;
cout<<"The count of Rect:"<
#include
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
复制代码
复制代码
在这段代码运行结束之前,会出现一个运行错误。原因就在于在进行对象复制时,对于动态分配的内容没有进行正确的操作。我们来分析一下:
在运行定义rect1对象后,由于在构造函数中有一个动态分配的语句,因此执行后的内存情况大致如下:
在使用rect1复制rect2时,由于执行的是浅拷贝,只是将成员的值进行赋值,这时 rect1.p = rect2.p,也即这两个指针指向了堆里的同一个空间,如下图所示:
当然,这不是我们所期望的结果,在销毁对象时,两个对象的析构函数将对同一个内存空间释放两次,这就是错误出现的原因。我们需要的不是两个p有相同的值,而是两个p指向的空间有相同的值,解决办法就是使用“深拷贝”。
3. 深拷贝
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:
复制代码
复制代码
#include
#include
using namespace std;
class Rect
{
public:
Rect()
{
p=new int(100);
}
Rect(const Rect& r)
{
width=r.width;
height=r.height;
p=new int(100);
*p=*(r.p);
}
~Rect()
{
assert(p!=NULL);
delete p;
}
private:
int width;
int height;
int *p;
};
int main()
{
Rect rect1;
Rect rect2(rect1);
return 0;
}
复制代码
复制代码
此时,在完成对象的复制后,内存的一个大致情况如下:
此时rect1的p和rect2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。
3. 防止默认拷贝发生
通过对对象复制的分析,我们发现对象的复制大多在进行“值传递”时发生,这里有一个小技巧可以防止按值传递——声明一个私有拷贝构造函数。甚至不必去定义这个拷贝构造函数,这样因为拷贝构造函数是私有的,如果用户试图按值传递或函数返回该类对象,将得到一个编译错误,从而可以避免按值传递或返回对象。
复制代码
复制代码
//防止按值传递
class CExample
{
private:
int a;
public:
//构造函数
CExample(int b)
{
a = b;
cout<<"creat: "<
1)有时候我们会遇到这样一种情况,我们用对象a初始化对象b后对象a我们就不在使用了,但是对象a的空间还在呀(在析构之前),既然拷贝构造函数,实际上就是把a对象的内容复制一份到b中,那么为什么我们不能直接使用a的空间呢?这样就避免了新的空间的分配,大大降低了构造的成本。这就是移动构造函数设计的初衷;
2)拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制;
3)C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况;
4)与拷贝类似,移动也使用一个对象的值设置另一个对象的值。但是,又与拷贝不同的是,移动实现的是对象值真实的转移(源对象到目的对象):源对象将丢失其内容,其内容将被目的对象占有。移动操作的发生的时候,是当移动值的对象是未命名的对象的时候。这里未命名的对象就是那些临时变量,甚至都不会有名称。典型的未命名对象就是函数的返回值或者类型转换的对象。使用临时对象的值初始化另一个对象值,不会要求对对象的复制:因为临时对象不会有其它使用,因而,它的值可以被移动到目的对象。做到这些,就要使用移动构造函数和移动赋值:当使用一个临时变量对象进行构造初始化的时候,调用移动构造函数。类似的,使用未命名的变量的值赋给一个对象时,调用移动赋值操作;
5)
Example6 (Example6&& x) : ptr(x.ptr)
{
x.ptr = nullptr;
}
// move assignment
Example6& operator= (Example6&& x)
{
delete ptr;
ptr = x.ptr;
x.ptr=nullptr;
return *this;
}
程序员定义的析构函数被扩展的过程?
1)析构函数函数体被执行;
2)如果class拥有成员类对象,而后者拥有析构函数,那么它们会以其声明顺序的相反顺序被调用;
3)如果对象有一个vptr,现在被重新定义
4)如果有任何直接的上一层非虚基类拥有析构函数,则它们会以声明顺序被调用;
5)如果任何虚基类拥有析构函数
析构函数的作用,如何起作用?
1)构造函数只是起初始化值的作用,但实例化一个对象的时候,可以通过实例去传递参数,从主函数传递到其他的函数里面,这样就使其他的函数里面有值了。规则,只要你一实例化对象,系统自动回调用一个构造函数,就是你不写,编译器也自动调用一次。
2)析构函数与构造函数的作用相反,用于撤销对象的一些特殊任务处理,可以是释放对象分配的内存空间;特点:析构函数与构造函数同名,但该函数前面加~。 析构函数没有参数,也没有返回值,而且不能重载,在一个类中只能有一个析构函数。 当撤销对象时,编译器也会自动调用析构函数。 每一个类必须有一个析构函数,用户可以自定义析构函数,也可以是编译器自动生成默认的析构函数。一般析构函数定义为类的公有成员。
类什么时候会析构?
1)对象生命周期结束,被销毁时;
2)delete指向对象的指针时,或delete指向对象的基类类型指针,而其基类虚构函数是虚函数时;
3)对象i是对象o的成员,o的析构函数被调用时,对象i的析构函数也被调用。
3.如果在类的析构函数中调用delete this,会发生什么?
会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存”。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
能否在析构函数 构造函数中抛出异常?
析构函数
1. 不要在析构函数中抛出异常!虽然C++并不禁止析构函数抛出异常,但这样会导致程序过早结束或出现不明确的行为。
2. 如果某个操作可能会抛出异常,class应提供一个普通函数(而非析构函数),来执行该操作。目的是给客户一个处理错误的机会。
3. 如果析构函数中异常非抛不可,那就用try catch来将异常吞下,但这样方法并不好,我们提倡有错早些报出来。
构造函数
总结如下:
1. 构造函数中抛出异常,会导致析构函数不能被调用,但对象本身已申请到的内存资源会被系统释放(已申请到资源的内部成员变量会被系统依次逆序调用其析构函数)。
2. 因为析构函数不能被调用,所以可能会造成内存泄露或系统资源未被释放。
3. 构造函数中可以抛出异常,但必须保证在构造函数抛出异常之前,把系统资源释放掉,防止内存泄露。(如何保证???使用auto_ptr???)
静态成员函数
与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。
#include
using namespace std;
class Myclass
{
private:
int a , b , c;
static int sum; //声明静态数据成员
public:
Myclass(int a , int b , int c);
static void GetSum(); //声明静态成员函数
};
int Myclass::sum = 0; //定义并初始化静态数据成员
Myclass::Myclass(int a , int b , int c)
{
this->a = a;
this->b = b;
this->c = c;
sum += a+b+c; //非静态成员函数可以访问静态数据成员
}
void Myclass::GetSum() //静态成员函数的实现
{
//cout<
关于静态成员函数,可以总结为以下几点:
有些情况下,允许特定的非成员函数访问一个类的私有成员,同时仍阻止一般的访问,这是很方便做到的。例如被重载的操作符,如输入或输出操作符,经常需要访问类的私有数据成员。
友元(frend)机制允许一个类将对其非公有成员的访问权授予指定的函数或者类,友元的声明以friend开始,它只能出现在类定义的内部,友元声明可以出现在类中的任何地方:友元不是授予友元关系的那个类的成员,所以它们不受其声明出现部分的访问控制影响。通常,将友元声明成组地放在类定义的开始或结尾是个好主意。
1、友元函数
友元函数是指某些虽然不是类成员函数却能够访问类的所有成员的函数。类授予它的友元特别的访问权,这样该友元函数就能访问到类中的所有成员。
复制代码
#include
using namespace std;
class A
{
public:
friend void set_show(int x, A &a); //该函数是友元函数的声明
private:
int data;
};
void set_show(int x, A &a) //友元函数定义,为了访问类A中的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
set_show(1, a);
return 0;
}
复制代码
2、友元类
友元类的所有成员函数都是另一个类的友元函数,都可以访问另一个类中的隐藏信息(包括私有成员和保护成员)。当希望一个类可以存取另一个类的私有成员时,可以将该类声明为另一类的友元类。
关于友元类的注意事项:
(1) 友元关系不能被继承。
(2) 友元关系是单向的,不具有交换性。若类B是类A的友元,类A不一定是类B的友元,要看在类中是否有相应的声明。
(3) 友元关系不具有传递性。若类B是类A的友元,类C是B的友元,类C不一定是类A的友元,同样要看类中是否有相应的申明。
复制代码
#include
using namespace std;
class A
{
public:
friend class C; //这是友元类的声明
private:
int data;
};
class C //友元类定义,为了访问类A中的成员
{
public:
void set_show(int x, A &a) { a.data = x; cout<
using namespace std;
class A; //当用到友元成员函数时,需注意友元声明与友元定义之间的互相依赖。这是类A的声明
class B
{
public:
void set_show(int x, A &a); //该函数是类A的友元函数
};
class A
{
public:
friend void B::set_show(int x, A &a); //该函数是友元成员函数的声明
private:
int data;
void show() { cout << data << endl; }
};
void B::set_show(int x, A &a) //只有在定义类A后才能定义该函数,毕竟,它被设为友元是为了访问类A的成员
{
a.data = x;
cout << a.data << endl;
}
int main(void)
{
class A a;
class B b;
b.set_show(1, a);
return 0;
}
友元小结:
在需要允许某些特定的非成员函数访问一个类的私有成员(及受保护成员),而同时仍阻止一般的访问的情况下,友元是可用的。
优点:
可以灵活地实现需要访问若干类的私有或受保护的成员才能完成的任务;
便于与其他不支持类概念的语言(如C语言、汇编等)进行混合编程;
通过使用友元函数重载可以更自然地使用C++语言的IO流库。
缺点:
一个类将对其非公有成员的访问权限授予其他函数或者类,会破坏该类的封装性,降低该类的可靠性和可维护性。
为什么友元函数必须在类内部声明?
因为编译器必须能够读取这个结构的声明以理解这个数据类型的大、行为等方面的所有规则。有一条规则在任何关系中都很重要,那就是谁可以访问我的私有部分。
介绍:
共用体,也叫联合体,在一个“联合”内可以定义多种不同的数据类型, 一个被说明为该“联合”类型的变量中,允许装入该“联合”所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的。union变量所占用的内存长度等于最长的成员的内存长度。
比较:
先看一个关于struct的例子:
struct student
{
char mark;
long num;
float score;
};
其struct的内存结构如下,sizeof(struct student)的值为12bytes。
下面是关于union的例子:
union test
{
char mark;
long num;
float score;
};
sizeof(union test)的值为4。因为共用体将一个char类型的mark、一个long类型的num变量和一个float类型的score变量存放在同一个地址开始的内存单元中,而char类型和long类型所占的内存字节数是不一样的,但是在union中都是从同一个地址存放的,也就是使用的覆盖技术,这三个变量互相覆盖,而这种使几个不同的变量共占同一段内存的结构,称为“共用体”类型的结构。其union类型的结构如下:
果冻想 | 一个原创文章分享网站
因union中的所有成员起始地址都是一样的,所以&a.mark、&a.num和&a.score的值都是一样的。
不能如下使用:
union test a;
printf("%d", a); //错误
由于a的存储区有好几种类型,分别占不同长度的存储区,仅写共用体变量名a,这样使编译器无法确定究竟输出的哪一个成员的值。
printf("%d", a.mark); //正确
应用:
测试大小端
union的一个用法就是可以用来测试CPU是大端模式还是小端模式:
复制代码
#include
using namespace std;
void checkCPU()
{
union MyUnion{
int a;
char c;
}test;
test.a = 1;
if (test.c == 1)
cout << "little endian" <