文章目录
->
->是指针的指向运算符,通常与结构体一起使用。
#include<stdio.h>
struct stu // 定义一个结构体
{
char name[10]; // 姓名
int num; // 学号
int age; // 年龄
};
void main()
{
struct stu *s; // 定义一个结构体指针
char str[]="ZhangLi";
s->name = str; // 对结构体中的成员变量name进行赋值
s->num = 2015120; // 对结构体中的成员变量num进行赋值
s->age = 18; // 对结构体中的成员变量age进行赋值
}
this指针
先要理解class的意思。class应该理解为一种类型,象int,char一样,是用户自定义的类型。用这个类型可以来声明一个变量,比如int x, myclass my等等。这样就像变量x具有int类型一样,变量my具有myclass类型。理解了这个,就好解释this了,my里的this 就是指向my的指针。如果还有一个变量myclass mz,mz的this就是指向mz的指针。 这样就很容易理解this 的类型应该是myclass ,而对其的解引用this就应该是一个myclass类型的变量。
通常在class定义时要用到类型变量自身时,因为这时候还不知道变量名(为了通用也不可能固定实际的变量名),就用this这样的指针来使用变量自身。
-
this指针的用处:
一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看成this的隐式使用。
this的目的总是指向这个对象,所以this是一个常量指针,我们不允许改变this中保存的地址。 -
this指针的使用:
一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。 -
this指针程序示例:
this指针是存在与类的成员函数中,指向被调用函数所在的类实例的地址。
根据以下程序来说明this指针
#include<iostream.h>
class Point
{
int x, y;
public:
Point(int a, int b) {
x=a; y=b;}
Void MovePoint( int a, int b){
x+=a; y+=b;}
Void print(){
cout<<"x="<<x<<"y="<<y<<endl;}
};
void main( )
{
Point point1( 10,10); //定义一个类对象
point1.MovePoint(2,2);
point1.print( );
}
当对象point1调用MovePoint(2,2)函数时,即将point1对象的地址传递给了this指针。
MovePoint函数的原型应该是 void MovePoint( Point *this, int a, int b);第一个参数是指向该类对象的一个指针,我们在定义成员函数时没看见是因为这个参数在类中是隐含的。这样point1的地址传递给了this,所以在MovePoint函数中便显式的写成:
void MovePoint(int a, int b) { this->x +=a; this-> y+= b;}
即可以知道,point1调用该函数后,也就是point1的数据成员被调用并更新了值。
即该函数过程可写成 point1.x+= a; point1. y + = b;
this指针指向当前的对象
对象a的内存地址和this指针的一模一样(都是0017F7E8);而当运行到对象b的时候,它的内存地址又和它所对应的this指针指向的内存地址一模一样了(都是0017F7DC)。这就说明了this指针变量记录的是当前对象的内存地址,即this指针指向当前的对象!
使用if和else必须用{}以防调窜
哑元:传入的参数不想用时可以只指定类型,写形参名不用会警告,改变传入的格式又很麻烦
1、哑元
a.) 只有类型没有参数名的参数,就叫哑元
如:int Fun(int a,int b,double); //如第三个参数的这种情况就叫哑元
b.) 哑元作用
1).向下兼容 如:函数 int Fun(int a,int b,double c);//2010年写的函数,在2011年的时候由于功能升级,不在需要第三个参,只需要将这个函数的第三参数必成如上函数即可。哑元。
2).实现重载(也可以只是为函数重载而用) 重载运算符(前项++,后项++在单目运算符重载的时候,以示区别。)
2、使用,例子代码:
namespace FunDummy
{
int Add(int a,int b,int)
{
return a+b;
}
int Add(int a,int c)
{
return a+c;
}
虚函数:基类中的虚函数允许派生类重写功能,编译器会保证派生类对象使用的是自己重写的功能
虚函数定义在基类中,其他继承此基类的派生类都可以重写该虚函数,因此虚函数是C++语言多态特性中非常重要的概念。但是派生类也可以重写基类中的其他的常规函数(非虚函数)呀,那为什么还要引入虚函数这样看起来很复杂的概念呢?
为什么要引入虚函数?
“猫吃老鼠”
转自https://baijiahao.baidu.com/s?id=1653132502323288772&wfr=spider&for=pc
上面这段C++语言代码定义了Animal(动物)和Cat(猫)两个类,其中Cat继承了Animal(猫显然是动物),我们可以在 main() 函数中使用这两个类:
Animal *animal = new Animal;
Cat *cat = new Cat;animal->eat();
// 输出: "I'm eating generic food."
cat->eat();
// 输出: "I'm eating a rat."
到这里一切都挺好的,动物吃食物,猫吃老鼠,即使不使用virtual关键字定义虚函数也完全没有问题。
现在稍稍修改下这段C++语言代码,引入另外一个函数func()(暂时不考虑实用性,仅做示例),它的代码如下,请看:
void func(Animal *xyz){
xyz->eat();
}
在 main() 函数中调用 func() 函数,相关的C++语言代码示例如下,请看:
Animal *animal = new Animal;
Cat *cat = new Cat;func(animal);
// 输出: "I'm eating generic food."
func(cat);
// 输出: "I'm eating generic food."
出问题了~请注意第二个 func() 函数调用,我们传递了一个Cat对象指针给它,但是输出的却不是“I’m eating a rat.”!仔细观察一下,*发现 func() 函数的参数类型是Animal xyz,那么为了让 func() 函数也能输出“I’m eating a rat.”,只能重载 func() 函数了:
void func(Animal *xyz){
xyz->eat();
}
void func(Cat *xyz){
xyz->eat();
}
现在 func() 函数能够根据输入参数类型的不同,输出不同的内容了。可是问题来了,Animal(动物)是一个基类,它能派生出的动物远远不止Cat(猫)一种,若是派生出许多派生类,每个派生类都重载一遍 func() 函数,是不是太麻烦了?
太麻烦了
神奇的虚函数
在C++语言中,重写基类中的常规(非虚)函数当然是可以的,但是从上面的例子可以看出,重写常规函数实现多态有时会带来非常麻烦的问题,要避免这样的问题可以使用 virtual function(虚函数)。现在,我们在 Animal 基类的 eat() 成员函数前加上virtual关键字:
加上virtual关键字
此时无需重载 func() 函数,仅保留一份func() 函数:
void func(Animal *xyz){
xyz->eat();
}
再执行下面的C++语言代码,输出就不同了:
func(animal);
// 输出: "I'm eating generic food."
func(cat);
// 输出: "I'm eating a rat."
看来,C++语言中的虚函数的确有些过人之处,有必要好好了解一下它。
事实上,每一个C++程序员都不可能避开虚函数的,就像每一个C语言程序员都不可能避开指针一样。
再总结下“什么是虚函数”
**基类中的虚函数允许派生类重写功能,编译器会保证派生类对象使用的是自己重写的功能,即使对象是通过基类指针访问的,例如前文中的 func(Animal *xyz) 函数,func(cat) 输出的实际上是 Cat 类重写的功能。**这是一个非常有用的特性,调用者甚至都不需要知道 Cat 等派生类的实现,因为只需使用基类 Animal 指针就能够轻易的调用所有派生类的重写功能。
基类的虚函数可以完全被重写,也可以部分的被重写,所谓的“部分被重写”,其实就是派生类在重写基类虚函数时,也可以调用基类虚函数的功能。
虚函数和常规函数被调用时有什么不同?
常规的非虚函数是静态解析的,即在编译时即可根据指针指向的对象确定是否被调用,例如文章开头的例子,如果 eat() 函数是非虚函数:
// eat() 函数不是虚函数
Animal *animal = new Animal;
Cat *cat = new Cat;
animal->eat();
// 输出: "I'm eating generic food."
cat->eat();
// 输出: "I'm eating a rat."
此时编译器在编译时就能确定 animal->eat() 调用的是 Animal::eat() 函数,cat->eat() 调用的是 Cat::eat() 函数。在 func(Animal *xyz) 函数中,因为其形参是 Animal *指针类型,所以即使传入的是 cat 对象指针,在 func() 函数内部也会被强制转换为Animal *指针,因此 func(cat) 调用的仍然是 Animal::eat() 函数。
而虚函数就不同了,它是动态解析的,也即在程序被编译后,运行时才根据对象的类型,而不是指向对象的指针类型决定其是否被调用,这就是说为的“动态绑定”。
关于“动态绑定”,我们在下一节中再做详细讨论,这里先留个印象。
在C++语言中,如果某个类有虚函数,那么大多数编译器都会自动的为其对象维护一个隐藏的“虚指针(virtul-pointer)”,虚指针指向一个全局“虚表(virtual-table)”,虚表中存放若干函数指针,这些函数指针指向类中的虚函数。请看下面这段C++语言代码:
class A {
public:
virtual void vfoo1();
virtual void vfoo2();
void foo1();
void foo2();
private:
int prv_i1, prv_i2;
};
显然,类 A 有两个常规函数以及两个 int 型的成员变量,此外,它还有两个虚函数,因此编译器会创建一个虚表,**虚表中存放的是函数指针,它们分别指向类 A 的虚函数,**如下图所示:
类A的虚表和虚函数
这里应注意,虚表是属于类的,而不是对象的,也就是说,即使有成千上万个 A 对象,虚表也仅有一个,这些对象共用一个类虚表。编译器会自动的为每个对象创建一个隐藏的“虚指针”__vptr,它指向类 A 的虚表,如下图所示:
隐藏的“虚指针”__vptr
C++语言这样实现虚函数机制的空间开销是微乎其微的,事实上,每一个对象只需要一个额外的“虚指针”__vptr就能够调用类的虚函数。同样的,时间开销也很小:相比于常规函数的调用,虚函数的调用只不过多出了额外的两个步骤:
获取虚表指针,得到虚表从虚表中取出虚函数的地址读者应注意,这里的讨论为了突出主题,理想化了一些情况。
为什么类成员函数默认都不是虚函数?
既然虚函数这么好用,那为什么C++语言不把类的成员函数默认定义为虚函数呢?
其实仔细考虑一下,虚函数的“好用”主要体现在定义在基类中实现多态上,但并不是所有的类都需要被设计为基类,一昧的使用虚函数可能会造成语义上的歧义,隐藏程序员的设计。仅将需要被继承的基类中需要被重写的函数定义为虚函数,要比将所有函数定义为虚函数清晰多了。
此外,经过前面的讨论,我们也知道虚函数的效率实际上是没有常规函数高的,同样的功能中,仅从被调用过程来看,它的时间开销和空间开销都比常规函数高,每个对象还需要额外的虚指针索引虚表。
QDebug 输出调试信息
原文链接:https://blog.csdn.net/Amnes1a/article/details/64438778
在程序开发过程中,我们经常需要打印一些调试信息到终端,以供我们开发人员快速定位软件的问题所在。而QDebug类就是用来完成这样的功能的一个类。使用QDebug类,我们可以将调试信息打印到控制台、文件、甚至某个字符串中。其构造函数如下:
QDebug(QIODevice *device)
QDebug(QString *string)
QDebug(QtMsgType type)
QDebug(const QDebug &other)
不过,通常情况下,我们并不直接创建这个类的对象来输出调试信息。因为,Qt中,为我们提供了相应的便捷函数,如qDebug(),改函数会为我们自动创建一个默认的QDebug对象。如下代码所示:
qDebug() << "Date:" << QDate::currentDate();
qDebug() << "Types:" << QString("String") << QChar('x') << QRect(0, 10, 50, 40);
qDebug() << "Custom coordinate type:" << coordinate;
其中,qDebug() 使用接收QtMsgType类型值得构造函数还创建一个QDebug() 对象。类似的,Qt还为我们提供了qWarning()、qCritical()、qFatal()、qInfo() 函数,根据不同的消息类型创建出不同的QDebug对象。QtMsgType的详细声明如下:
> Constant Value Description QtDebugMsg 0 A message generated by the
> qDebug() function QtWarningMsg 1 A message generated by the
> qWarning() function QtCriticalMsg 2 A message generated by the
> qCritial() function QtFatalMsg 3 A message generated by the qFatal()
> function QtInfoMsg 4 A message generated by the qInfo() function
> QtSystemMsg QtCrtitialMsg
QDebug会自动格式化其输出,以使输出更可读。它会自动地在参数中间添加空格,并在输出QString、QByteArray、QChar参数时,自动为这些内容添加双引号。当然,我们也可改变这些默认选项,通过使用相关的成员函数,比如space()、nospace()、quote()、noquote()。而且,文本流中常用的格式符也可以在QDebug中使用。
QDebug中已经针对很多数据类型为我们重载了<< 运算符,能满足我们一般的调试需求。若实在想添加对自己的某种自定义类型的支持,也可以自己再重载<< 即可。如下面的例子所示:
struct Student
{
QString name;
QString sex;
};
QDebug operator<<(QDebug debug, const Student &s)
{
QDebugStateSaver saver(debug);
debug.nospace() << '[' << s.name << ", " << s.sex << ']';
return debug;
}
其中,用到了QDebugStateSaver类。该类其实就是在自定义QDebug运算符时,先保存QDebug当当前设置,然后我们可以根据自动的需要修改相关设置,进而输入我们需要的格式信息;当saver对象,超出作用域进行析构时,会自动恢复QDebug的状态。
至于QDebug的成员函数,我目前主要是使用noquote() 函数。上面我们说过,QDebug在输出QString 和 QByteArray等类型的内容时,会自动为它们添加双引号。而有时,在反而会让控制台的信息更密集,不便于快速分析结果。所以,我经常使用该函数,修改此默认设置。如下代码所示:
#include <QCoreApplication>
#include <QDebug>
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);