C++进阶


namespace

在学习QT的过程中遇到一句

namespace Ui { class Widget; }

没能很理解是什么意思啊,为什么在QT中要这么定义一句命名空间,毕竟在一般的学习过程中基本都不怎么使用namespace的,所以对这一块的知识可能不是很了解,然后就是再学一下了。

1、单文件的命名空间使用

先写一段代码

#include<iostream>

using namespace std;

namespace my{
    int add(int a,int b){   //写在命名空间的函数
        return a+b;
    }

}

int add(int a,int b){   //一般的普通函数
    return a+b+1;    
}
int main(int argc,char** argv){
    int a=5,b=5;
    cout<< add(a,b) <<endl;
    cout<< my::add(a,b) <<endl;
    return 0;
}

运行结果

命名空间其中之一的功能就是帮我们解决命名冲突的问题,这里有两个add()函数但是都可以正常的运行使用,不过这么看的话总感觉有种脱裤子放屁的感觉,但是啊这就是在单文件中的使用,如果要实际做项目的话肯定不可能是单文件的,就像做QT应用程序,再简单也会分出很多文件出来,这个时候命名空间的作用显现出来了,那么这东西到底是干什么的,专业术语的解释是命名空间是对作用域的一种抽象,举个例子,假如A班有个人叫张三,B班也有个人叫张三,不过这不会互相影响,因为你知道他们不是一个班的,如果要找A班的张三那么就不会去找B班的张三了。

在实际的项目当中,会出现成千上万的标识符,命名空间提供隐藏区域标识符的机制。通过将逻辑上相关的标识符构成响应的命名空间,可以使整个系统更加的模块化。

2、多文件的命名空间使用

分别编写test.h test.cpp main.cpp

test.h

#ifndef _TEST_H
#define _TSTE_H
#include<iostream>

namespace my{     //在命名空间声明一个类
    class Point{
        public:
            Point()=default;
            Point(double a,double b);
            void info_() const {
                std::cout << "Point x="<< x <<" y="<< y << std::endl;
            }
            ~Point();
        private:
            double x{0.0};
            double y{0.0};
    };
}
#endif

test.cpp

#include "test.h"

namespace my{
    Point::Point(double a,double b):x(a),y(b){}
    
    Point::~Point(){}
}

main.cpp

#include <iostream>
#include "test.h"
int main(){
    my::Point a(3.4,4.5);
    a.info_();
    return 0;
}

编译运行

成功运行了嗷

3、在多文件下命名空间写函数

在单文件中我们在命名函数中写了一个add函数,多文件操作有点不同,例如

直接把函数放在头文件按中

并没有报错编译一下

编译器报错

具体原因我也不是很清楚,这里应该现在test只声明add()函数,在test.cpp实现add()函数

这样改了就可以正常编译了

还有一点值得注意的就是同一项目的不同文件中可以定义相同的命名空间我这里就只定义了一个头文件,也就是如果我定义了两个头文件,在另一个头文件中同样可以使用namespace my{}分散在不同文件中的同名命名空间会合并为一个,所以这同时也说明了就算不是在一个文件下,但是只要共用一个命名空间就保证命名空间中成员互不相同,否则就会报错出重复定义的错误,就相当于在你的main函数下同一作用域下写了两个int a;

4、using使用

在我们刚开始写c++代码的时候就都用过using namespace std这一句,没有这一句就不能使用,当然了也可以用std::cout这样指定作用域使用

只引入函数

在没有引入函数的时候我们直接在主函数使用add()函数是会报错的,如图

编译器报错未定义标识符,这个时候就可以从命名空间引入add()函数

#include <iostream>
#include "test.h"
int main(){
    my::Point a(3.4,4.5);
    a.info_();
    
    using my::add; //引入函数
    std::cout << add(3,4) <<std::endl;
    
    return 0;
}

这样就不会报错,可以正常运行了

只引入类

跟上面类似在没有引入的时候是不能直接使用命名空间中的类的

using my::Point;
Point i(8.9,5.6);
i.info_();

引入整个命名空间

我们在命名空间中再加一个add_two()函数,然后引入命名空间并使用这个函数

using namespace my;
std::cout <<add_two(2,3)<<std::endl;

完整main.cpp

#include <iostream>
#include "test.h"
int main(){
    my::Point a(3.4,4.5);
    a.info_();
    {
        using my::add;
        std::cout << add(3,4) <<std::endl;
    }

    {
        using my::Point;
        Point i(8.9,5.6);
        i.info_();
    }

    {
        using namespace my;
        std::cout <<add_two(2,3)<<std::endl;
    }
    return 0;
}

5、QT头文件中的namespace Ui { class Widget; }

class Widget 里面有个声明 Ui::Widget *ui,这个 ui 是使用 namespace Ui 里的 Widget 类声明的,该类只是简单的继承了 ui_widget.h 里的 Ui_Widget 类(没有添加任何成员)。现在就很清楚了,这两个看起来名字一样的 Widget 其实是两个类,一个是 namespace Ui 里的,另一个是 namespace Ui 之外的 Widget 类,namespace 声明的类其实就是个空壳,它的基类功能是将此窗口上的所有控件的声明、实例化、初始化。声明的原因就是为了使 ui 布局控制和其他的控制代码分离。

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTextToSpeech> //添加说话类

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = 0);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    QTextToSpeech *x;
};

#endif // WIDGET_H

在Qt Creator自动创建的项目中,使用了组合的方式来使创建的窗体,将UI_Widget作为一个成员变量来使用,也就是

private:
    Ui::Widget *ui

写程序时要使用组件就要用ui->的方式比如

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    x = new QTextToSpeech;
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    x->say(ui->lineEdit->text()); //提取框中文字语音播报
}

const 指针

常量指针 const修饰指针

const int *p=&a;

特点:指针的指向可以修改,(此时指针指向变量a),指针指向的值不可修改(此时指针指向的值为变量a内存空间中的10)。

指针常量 const修饰常量

int *const p=&a;

特点:指针的指向不可以修改,(此时指针指向变量a),指针指向的值可修改(此时指针指向的值为变量a内存空间中的10)。

const既修饰指针,又修饰常量

const int *const p=&a;

特点:两者都不可以改。

静态成员

静态成员变量

在两个类中定义静态成员变量

class test{
	public:
		static int num;
    	int a;
};

这个类创建了一个静态成员变量,static成员变量属于类,无论创建多少对象,只分配一份内存,但是里面的int a就不是共用的一份内存,每创建一个对象就会分一块内存给a

static成员必须在类声明的外部进行初始化

int test01::num = 0;

无论是被public,protected,private修饰的静态成员,都是使用这个种方式初始化。

static成员变量的内存分配既不是在声明类时分配,也不是创建对象时分配,而是在类外初始化的时候分配的

访问方式

//通过类访问
test::num = 5;
//通过对象访问
test a;
a.num = 6;
//通过对象指针访问
test *p = new a;
p->num = 10;

static储存在全局区,即使不创建对象也是可以访问的

静态成员函数

静态成员函数只能访问静态成员

编译器在编译一个普通成员函数时,会隐式地增加一个形参 this,并把当前对象的地址赋值给 this,所以普通成员函数只能在创建对象后通过对象来调用,因为它需要当前对象的地址。而静态成员函数可以通过类来直接调用,编译器不会为它增加形参 this,它不需要当前对象的地址,所以不管有没有创建对象,都可以调用静态成员函数。

静态成员函数与普通成员函数的根本呢区别在于,普通成员函数有this指针,可以任意访问类中的成员,但是静态成员函数没有this指针,只能访问静态成员

静态成员函数的主要目的是调用静态成员变量

class test{
	public:
		static int my();//声明静态成员函数
	private:
		static int num;
};
int test::num = 10;
int::my(){//定义静态成员函数
	return num;
}
int main(){
	std::cout<<test::my();
	return 0;	
}

转换函数

  • 转换函数必须是类方法
  • 转换函数不能指定返回类型
  • 转换函数不能有参数
class test{
	public:
		test(int a,int b):m(a),n(b){}
    	operator double() const{
			return (double)a/b;
        }
    private:
    	int m;
    	int n;
};

int main(){
	test a(4,7);
    double sum = 1+a;
    std::cout<<sum;
    return 0;
}

这里使用了转换函数

  • operator表示这是一个运算符函数
  • double()是函数名,表明了把test类型转换成double,这里编译器根据函数返回值决定了返回类型所以就没有定义返回值
  • const表示在该函数中不会去改变变量的值
  • return (double)a/b 因为a和b都是int类型这里强转为double。
  • double sum = 1+a 运行到这里编译器会先找+号的运算符重载不过没有找到,又发现转换函数可以将test转换成double。

左移运算符

左移运算运算的是二进制,通过左移不同的位数可以实现不同的功能,比如左移0位就相当于取整操作,左移1位就相当于将操作数乘以2

示例

计算树中深度相同的节点的和最大的值,并输出深度

#include <iostream>
using namespace std;
typedef long long ll;
int main(){
    int a,b;//节点数和节点权数
    int cont;//计数,因为完全二叉树的最后一层不一定是满的
    cin>>a;
    ll Maxsum = 0;//最大的层数和
    bool flag = false;
    int MaxN;//最大层数
    int i =0;
	for(i; ; i++){//每层的层数从0开始计算
        ll sum = 0;//每层的和
        for(int N = 0;N<(1<<i);N++){//利用左移运算符号输入每层的数据
            cin>>b; sum+=b;
            if(++cont>=a){
                flag = true;
                break;
            }
        }
        if(Maxsum<sum){
            Maxsum=sum; MaxN = i+1;
        }
        if(flag)
            break;
    }
    cout <<MaxN;
    return 0;
}

new创建二维数组

在练习dp杨辉三角的时候遇到的,用new创建杨辉三角

#include<bits/stdc++.h>
using namespace std;
int main()
{
  int n;
  cin>>n;
   //一维数组的写法是  int* a = new int[n];
  int** a = new int*[n];
  for(int i =1;i<=n;i++){
    a[i] = new int[i];
  }
  for(int i = 1;i<=n;i++){
    for(int j =1;j<=i;j++){
      cin>>a[i][j];
    }
  }
  for(int i = 1;i<=n;i++){
    for(int j =1;j<=i;j++){
      cout<<a[i][j]<<" ";
    }
    cout<<endl;
  }
   //释放内存
  for (int i = 1;i <= n;i++) 	delete[]a[i];
  delete[]a;  
  return 0;
}

在类中使用初始化列表的问题

#include<iostream>
class A{
public:
    A(int t):a(t),b(a){}
    void info_(){
        std::cout<<"a = "<<a<<" b = "<<b;
    }
private:
    int b;
    int a;
};
int main(){
    A aa(10);
    aa.info_();
    return 0;
}

这段代码的输出结果是

a = 10 b = 垃圾值;

产生这种问题的原因是在初始化列表中的初始话顺序并不是写的顺序,而是按照定义的顺序来的,先定义的b,所以先初始话b,b是a的值,但是此时a还没有赋值,所以b存的就是一个垃圾值。

如果将定义顺序改为

int a;
int b;

则得到的就是我们想要的结果。

当然这种定义顺序的要求仅仅是对于初始化列表的,如果将代码改成

#include<iostream>
class A{
public:
    A(int t){
        a=t;
        b=a;
    }
    void info_(){
        std::cout<<"a = "<<a<<" b = "<<b;
    }
private:
    int b;
    int a;
};
int main(){
    A aa(10);
    aa.info_();
    return 0;
}

依旧是我们想要的结果。

初始化列表里赋值跟定义顺序有关,在构造函数里跟定义顺序无关


文章作者: zhang
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 zhang !
  目录