c++知识扫盲

本文详细介绍了C++中的this指针,讲解了其在类成员函数中的作用和使用场景,强调了其在多态中的重要性。此外,还探讨了使用if和else时使用{}的重要性,以及哑元参数的处理方法。接着,深入剖析了虚函数的概念,解释了为什么需要虚函数以及其动态绑定的机制。文章还涵盖了QDebug用于输出调试信息的功能,宏的使用注意事项,以及Qt中的信号和槽机制,包括Q_OBJECT宏的作用。最后,讨论了C++中set数据结构的特点和操作,以及++运算符的前后顺序和char与string的区别。
摘要由CSDN通过智能技术生成

->

->是指针的指向运算符,通常与结构体一起使用。

#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这样的指针来使用变量自身。

  1. this指针的用处:
    一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
    this作用域是在类内部,当在类的非静态成员函数中访问类的非静态成员的时候,编译器会自动将对象本身的地址作为一个隐含参数传递给函数。也就是说,即使你没有写上this指针,编译器在编译的时候也是加上this的,它作为非静态成员函数的隐含形参,对各成员的访问均通过this进行。例如,调用date.SetMonth(9) <===> SetMonth(&date, 9),this帮助完成了这一转换 .
    在成员函数内部,我们可以直接使用调用该函数的对象的成员,而无需通过成员访问运算符来做到这一点,因为this所指的正是这个对象。任何对类成员的直接访问都被看成this的隐式使用。
    this的目的总是指向这个对象,所以this是一个常量指针,我们不允许改变this中保存的地址。

  2. this指针的使用:
    一种情况就是,在类的非静态成员函数中返回类对象本身的时候,直接使用 return *this;另外一种情况是当参数与成员变量名相同时,如this->n = n (不能写成n = n)。

  3. 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

定义了Animal(动物)和Cat(猫)两个
上面这段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);
 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值