c++ 用啥查啥记啥

c++ system

0.问题+细节

clion 中的一些细节问题:
*(int(*)[10])(a)

ps: 我的数组名为a, int类型, 10个元素

输出格式:

不以科学计数法输出

1、在头文件包含—#include——定义IO流输出输入格式控制相关函数。

2、利用cout输出格式为—cout << fixed << setprecision(0) << temp << endl;—temp为输出数据。

【分析】——主要涉及到cout的格式化输出方面的知识

1、cout<<fixed——指一般方式(浮点)输出,不采用科学计数法输出

2、setprecision(n)——设置输出浮点数的精度,配合fixed设定,指的是小数位数,设置setprecision(0)即指输出不带小数位的int值。

vector的两种访问方式——vector[]与vector.at()的区别

vector[]和vector.at()的区别
b.v[]和b.at()都可以对v中元素进行访问,并且访问的元素时都不能越界,比如a[10]或a.at(10)这样的使用会报错。区别在于,operator[]不做边界检查, 哪怕越界了也会返回一个引用,当然这个引用是错误的引用,如何不小心调用了这个引用对象的方法,会直接导致应用退出。而由于at会做边界检查,如果越界,会抛出异常,应用可以try catch这个异常,应用还能继续运行。

数组:
  • int a[] 和 int a区别*

先说结论: 做形参的时候,int *aint a[]无任何区别。

int *a //的 a 是一个指针变量,可以对a赋其他值,或者a++,a-- 的运算。
int a[] // 的 a 是一个指针常量
  • 数组大小可变:

C++中动态改变数组大小的三种函数malloc、calloc、realloc

2021-06-09 16:08:03

malloc

作用:可以提前给数组分配好大小。

参数:只有一个,sizeof(类)*n,计算一共需要n个类的大小,即n个对象。

强制类型转换:malloc函数的返回类型默认为void *,所以要强制转换为指向类的指针类型。

数据初始化:malloc申请空间成功后,并不会初始化,分配到的空间中的数据都是随机数。

代码:向系统申请3个student大小的内存空间。

student *p2=(student *)malloc(sizeof(student)*3);

calloc

作用:可以提前给数组分配好大小。

参数:有两个,(sizeof(类),n) 一个类的空间大小、n是类的个数。

强制类型转换:calloc函数的返回类型默认为void *,所以要强制转换为指向类的指针类型。

数据初始化:calloc申请空间成功后,会初始化,分配到的空间中的数据都初始化为0。

代码:向系统申请3个student大小的内存空间。

student* st = (student*)calloc(sizeof(student), 3);

realloc

作用:可以动态的增加数组大小,即随时可以追加空间。

参数:有两个,(p,sizeof(类)*n) p是要改变内存大小的指针名、后面是新的大小。

强制类型转换:realloc函数的返回类型默认为void *,所以要强制转换为指向类的指针类型。

数据初始化:realloc申请空间成功后,不会初始化。

代码:追加3个空间大小。

st = (student*)realloc(st, sizeof(student)*(6));
C++继承模板类,需要使用this指针或者Base::调用成员变量

原因:写一个基类-十大排序算法的共同的数组/vector/len等变量和初始化过程。

写一个继承类-分别代表各个排序算法。(涉及到多态的使用 - virtual 虚函数)

对上面的类变成 模板类:应对各种类型的数组和vector

涉及:

  • 类的继承:基类和继承类
  • 模板类和模板函数
  • 多态-virtual:其实在这效果未体现出来

问题: 继承模板类对基模板类的public和protect的变量调用不了,显示未定义。

解决

//父类是模板可以用Base::来调用父类成员
SeqList<T>::m_array = space;
//父类是模板可以用this来调用父类成员
this->length = 0;

1. 智能指针 std::shared_ptr

它的主要作用如下:

1、智能指针主要的用途就是方便资源的管理,自动释放没有指针引用的资源。

2、使用引用计数来标识是否有多余指针指向该资源。(注意,shart_ptr本身指针会占1个引用)

可以用 shared_ptr.use_count() 函数查看这个智能指针的引用计数,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除

2. 线程 C++ std::thread:

c++ 11 以后有了标准的线程库。

博客圆

1.std::thread线程详解(1)

与进程相比,其所需的资源更少,线程之间沟通的方法更多; 他们之间的区别可以比较简明用以下几点概括[1]:

  1. 进程是资源分配的最小单位,线程是CPU调度的最小单位;也就是说进程之间的资源是相互隔离,而线程之间的是可以相互访问的。
  2. 线程的存在依赖于进程,一个进程可以保护多个线程;
  3. 进程出现错误不会影响其他进程,但是一个线程出现错误,会影响同一进程下的所有线程。

线程的创建

std::thread t1([]() {
  std::cout << "Hello World" << std::endl;
});
t1.join();

一般情况下会使用printf输出 与std::cout << "Hello World" << std::endl; 相比较而言。

线程的方法和属性

  1. joinable()判断线程是否可连接(可执行线程)的,有以下情况的,为不可连接的:
    1. 构造时,thread()没有参数;
    2. 该对象的线程已经被移动了;
    3. 该线程已经被joindetach
  2. get_id() 返回线程的ID;
  3. native_handle() 返回POSIX标准的线程对象;
  4. join() 等待线程执行完成;
  5. detach() 分离线程,分离后对象不再拥有线程。该线程结束后,会自动回收内存。(并不会开启另一个进程);
  6. swap() 交换对象的线程。

std::jthread (C++20)#

  • 除了常用的std::thread外,标准库还存在着另一个可以创建线程的类,std::jthread。他们之间的差别比较明显的就是,std::jthread会在解构的时候判断线程是否还在运行joinable,如果还在运行则自动调用request_stopjoin
  • 除此之外,std::jthread还提供了一个内置的std::stop_token。可以通过线程函数的第一个参数来获取(如果函数的第一个参数类型为std::stop_token)。
  • 可以通过get_stop_sourceget_stop_tokenrequest_stop等方法来对其进行操作。

stop_token (C++20)#

stop_token类似于一个信号,告诉线程是否到了结束的时候。和stop_source一起使用。stop_token用来获取是否退出(读),而stop_source用来请求推出(读写)。其方法:

  1. request_stop 请求退出
  2. stop_requested 获取是否已经请求退出
  3. stop_possible 获取是否可以请求退出
void thread_func(std::stop_token token) {
    int data = 0;
    while (!token.stop_requested()) {
        printf("%d\n", data);
        data++;
        std::this_thread::sleep_for(1s);
    }
    printf("Exit\n");
}

int main() {
    std::jthread mythread(thread_func);

    std::this_thread::sleep_for(4s);

    return 0;
}

本次讲述了线程创建的一些方法,可以看到相比较C语言而言,由于C++11提出的函数对象(普通函数、匿名函数,std::bind的输出等)使得线程的创建更加的方便

2.std::thread线程库详解(2)

简介#

上一篇博文中,介绍了一下如何创建一个线程,分别是std::threadstd::jthread (C++20)。这两种方法相似,std::jthread相对来说,更加方便一些,具体可以再看看原来的博文,std::thread线程详解(1)

这一次,我将介绍一下,多线程的。锁在多线程中是使用非常广泛的。是多线程中最常见的同步方式。主要介绍的锁有mutexrecursive_mutexshared_mutex

最基本的锁 std::mutex

使用

std::mutex是最基本的锁,也是最常见的锁。它提供了最基本的多线程编程同步方法。

using namespace std::chrono_literals;

std::mutex g_mutex;

void thread_func() {
    g_mutex.lock();
    std::cout << "Thread out 1: " << std::this_thread::get_id() << std::endl;;
    std::this_thread::sleep_for(1s);
    std::cout << "Thread out 2: " << std::this_thread::get_id() << std::endl;;
    g_mutex.unlock();
}

int main() {
    std::cout << "Mutex Test." << std::endl;
    std::thread thread1(thread_func);
    std::thread thread2(thread_func);
    thread1.join();
    thread2.join();
    return 0;
}

以上示例中,只有一个线程函数thread_func,它的工作很简单:

首先对g_mutex加锁,然后输出一段字符串,接着休眠1s,输出第二段字符串,最后对g_mutex进行解锁。

输出结果如下:

image-20211108211354690

方法和属性:

  • lock() 为对象加锁,如果已经被锁了,则阻塞线程;
  • try_lock() 尝试加锁,如果已经被加锁,则返回false,否则将对其进行加锁并返回true;
  • unlock() 为对象解锁,通常和加锁(lock()try_lock())成对出现;
  • native_handle() 返回锁的POSIX标准对象。

递归锁 std::recursive_mutex:

std::recursive_mutex是一个递归锁,方法和使用都和std::mutex类似。唯一的不同是,std::mutex在同一时间,只允许加锁一次,而std::revursive_mutex允许同一线程下进行多次加锁。如:

// 定义递归锁
std::recursive_mutex g_mutex;

// 线程函数
void thread_func(int thread_id, int time) {
    g_mutex.lock();
    std::cout << "Thread " << thread_id << ": " << time << std::endl;
    if (time != 0) thread_func(thread_id, time - 1);
    g_mutex.unlock();
}

// 初始化线程
std::thread thread1(thread_func, 1, 3);
std::thread thread2(thread_func, 2, 4);

这一次的方法和之前的略有不同,为了更加直观的观察不同的线程,这次是在输入的时候输入一个标志来区分不同的线程。可以清楚的看到,这是一个递归函数,每次调用的时候都将time减少1,直到其变为0。需要注意的是,在递归的时候并没有释放锁,而是直接进入,因此在第二层遍历的时候,又会对g_mutex进行一次加锁,如果是普通的锁,次数将会阻塞进程,变成死锁。但是此时使用的是递归锁,它允许在同一个线程,多次加锁,因此这个程序可以成功运行,并获得输出

结果如下:

image-20211108211804013

共享锁 std::shared_mutex (C++17)

std::shared_mutex在C++14已经存在了,但是在C++14中的std::shared_mutex是带timing的版本的读写锁(也就是说,C++14中的std::shared_mutex等于C++17中的std::shared_timed_mutex)。读写锁有两种加锁的方式,一种是shared_lock(),另一种lock()shared_lock是读模式,而lock是写模式。读写锁允许多个读加锁,而写加锁和其他所有加锁互斥。即同一时间下:

  • 允许多个线程同时读;
  • 只允许一个线程写;
  • 写的时候不允许读,读的时候不允许写。
// 共享锁
std::shared_mutex g_mutex;

// 读线程 1
void thread_read_1_func(int thread_id) {
    // 第一个获取读权限
    g_mutex.lock_shared();
    std::cout << "Read thread " << thread_id << " out 1." << std::endl;
    // 睡眠2s,等待读线程2,获取读权限,确认可以多个线程进行读加锁
    std::this_thread::sleep_for(2s);
    std::cout << "Read thread " << thread_id << " out 2." << std::endl;
    // 解锁读
    g_mutex.unlock_shared();
}

void thread_read_2_func(int thread_id) {
    // 睡眠500ms,确保读线程1先获取锁
    std::this_thread::sleep_for(500ms);
    g_mutex.lock_shared();
    std::cout << "Read thread " << thread_id << " out 1."  << std::endl;
    std::this_thread::sleep_for(3s);
    std::cout << "Read thread " << thread_id << " out 2."  << std::endl;
    g_mutex.unlock_shared();
}

void thread_write_1_func(int thread_id) {
    // 确保读线程先获得锁,确认读写互斥
    std::this_thread::sleep_for(300ms);
    g_mutex.lock();
    std::cout << "Write thread " << thread_id << " out 1."  << std::endl;
    g_mutex.unlock();
}

总结

本文主要介绍了三种不同的锁,普通锁,递归锁,读写锁。三个锁有着不一样的使用方法,但是可以确定的是,过多的使用锁,会导致程序中的串行部分过多,并行效果不好。因此对于锁的使用,需要尽量的克制,尽量的合理。

std::thread线程库详解 3:

前两篇的博文分别介绍了标准库里面的线程和锁,这一次的博文将会介绍锁的管理。

锁在多线程编程中非常常用,但是一旦使用不谨慎就会导致很多问题,最常见的就是死锁问题。

lock_guard

std::lock_guard是最常见的管理锁的类,它会在初始化的时候自动加锁,销毁的时候自动解锁,需要锁的对象满足BasicLockable,即存在lockunlock方法。测试代码:

void thread_func(int thread_id) {
  {
    std::lock_guard<std::mutex> guard(global_mutex);
    std::cout << "Test 1:" << thread_id << std::endl;
    std::this_thread::sleep_for(1s);
    std::cout << "Test 2:" << thread_id << std::endl;
  }
  std::this_thread::sleep_for(0.5s);
  std::cout << "Test 3:" << thread_id << std::endl;
}

4.

前言

本文主要介绍了多线程中的条件变量,条件变量在多线程同步中用的也比较多。我第一次接触到条件变量的时候是在完成一个多线程队列的时候。条件变量用在队列没有数据时,等待入队线程入队数据。相比较于锁的使用,条件变量的使用更为复杂,使用时需要注意的部分也更多。本文将会完成一个阻塞队列(对普通队列进行一个简单的包装),以此来完成条件变量的介绍。

5.

前面四部分内容已经把目前常用的C++标准库中线程库的一些同步库介绍完成了,这一次我们探讨的都是C++20中的内容。主要有两个部分,信号量和latch与barrier。

由于GCC使用的libstdc++还没有完成这一部分特性,所以我们使用的是LLVM的libc++来进行实验,鉴于gcc更换标准库比较麻烦,所以我们使用的是clang编译器。在编译的时候添加选项-stdlib=libc++ -std=gnu++2a

【菜鸟教程】

默认构造函数thread() noexcept;
初始化构造函数template <class Fn, class… Args>
explicit thread(Fn&& fn, Args&&… args);
  • 默认构造函数,创建一个空的 std::thread 执行对象。
  • 初始化构造函数,创建一个 std::thread 对象,该 std::thread 对象可被 joinable,新产生的线程会调用 fn 函数,该函数的参数由 args 给出。

**注意:*可被 joinable std::thread 对象必须在他们销毁之前被主线程 join 或者将其设置为 detached.

std::lock_guard

使用lock_guard则相对安全,它是基于作用域的,能够自解锁,当该对象创建时,它会像**m.lock()**一样获得互斥锁,当生命周期结束时,它会自动析构(unlock),不会因为某个线程异常退出而影响其他线程

其他成员函数

get_id: 获取线程 ID,返回一个类型为 std::thread::id 的对象。请看下面例子:

joinable: 检查线程是否可被 join。检查当前的线程对象是否表示了一个活动的执行线程,由默认构造函数创建的线程是不能被 join 的。另外,如果某个线程 已经执行完任务,但是没有被 join 的话,该线程依然会被认为是一个活动的执行线程,因此也是可以被 join 的。

detach: Detach 线程。 将当前线程对象所代表的执行实例与该线程对象分离,使得线程的执行可以单独进行。一旦线程执行完毕,它所分配的资源将会被释放。

swap: Swap 线程,交换两个线程对象所代表的底层句柄(underlying handles)。

native_handle: 返回 native handle(由于 std::thread 的实现和操作系统相关,因此该函数返回与 std::thread 具体实现相关的线程句柄,例如在符合 Posix 标准的平台下(如 Unix/Linux)是 Pthread 库)。

std::this_thread 命名空间中相关辅助函数介绍

get_id: 获取线程 ID。

yield: 当前线程放弃执行,操作系统调度另一线程继续执行。

sleep_until: 线程休眠至某个指定的时刻(time point),该线程才被重新唤醒。

sleep_for: 线程休眠某个指定的时间片(time span),该线程才被重新唤醒,不过由于线程调度等原因,实际休眠时间可能比 sleep_duration 所表示的时间片更长。

3. throw ; try…catch ; c++异常处理基本语法

C++ 通过 throw 语句和 try…catch 语句实现对异常的处理。throw 语句的语法如下:

throw 表达式;

该语句拋出一个异常。异常是一个表达式,其值的类型可以是基本类型,也可以是类。

try…catch 语句的语法如下:

try {
  语句组
}
catch(异常类型) {
  异常处理代码
}
...
catch(异常类型) {
  异常处理代码
}

catch 可以有多个,但至少要有一个。

不妨把 try 和其后{}中的内容称作“try块”,把 catch 和其后{}中的内容称作“catch块”。

try…catch 语句的执行过程是:

  • 执行 try 块中的语句,如果执行的过程中没有异常拋出,那么执行完后就执行最后一个 catch 块后面的语句,所有 catch 块中的语句都不会被执行;
  • 如果 try 块执行的过程中拋出了异常,那么拋出异常后立即跳转到第一个“异常类型”和拋出的异常类型匹配的 catch 块中执行(称作异常被该 catch 块“捕获”),执行完后再跳转到最后一个 catch 块后面继续执行。

例如下面的程序:

#include <iostream>
using namespace std;
int main()
{
    double m ,n;
    cin >> m >> n;
    try {
        cout << "before dividing." << endl;
        if( n == 0)
            throw -1; //抛出int类型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch(double d) {
        cout << "catch(double) " << d <<  endl;
    }
    catch(int e) {
        cout << "catch(int) " << e << endl;
    }
    cout << "finished" << endl;
    return 0;
}

程序的运行结果如下:
9 6↙
before dividing.
1.5
after dividing.
finished

说明当 n 不为 0 时,try 块中不会拋出异常。因此程序在 try 块正常执行完后,越过所有的 catch 块继续执行,catch 块一个也不会执行。

程序的运行结果也可能如下:
9 0↙
before dividing.
catch(int) -1
finished

当 n 为 0 时,try 块中会拋出一个整型异常。拋出异常后,try 块立即停止执行。该整型异常会被类型匹配的第一个 catch 块捕获,即进入catch(int e)块执行,该 catch 块执行完毕后,程序继续往后执行,直到正常结束。

如果拋出的异常没有被 catch 块捕获,例如,将catch(int e),改为catch(char e),当输入的 n 为 0 时,拋出的整型异常就没有 catch 块能捕获,这个异常也就得不到处理,那么程序就会立即中止,try…catch 后面的内容都不会被执行。

4. 模板

/**
 * 模板是C++支持参数化多态的工具,使用模板可以使用户为类或者函数声明一种一般模式,使得类中的某些数据成员或者成员函数的参数、
 * 返回值取得任意类型。
 *
模板是一种对类型进行参数化的工具;
通常有两种形式:[函数模板]和[类模板];
函数模板针对仅参数类型不同的函数
类模板针对仅数据成员和成员函数类型不同的类。
 */
//一、函数模板
/* 1.1 模板的通用格式
template <typename 形参名,typename 形参名,......>
返回类型 函数名(参数列表)
{
    函数体
}
 其中template和class是关键字,class可以用typename 关见字代替,在这里typename 和class没区别,
 <>括号中的参数叫模板形参,模板形参和函数形参很相像,模板形参不能为空。
 eg:swap的实例函数
 template <typename T>
 void swap(T& a, T& b){},
 */


//二、类模板
/* 2.1 类模板的格式为:
template<class  形参名,class 形参名,…>
 class 类名
{ ... };
 类模板和函数模板都是以template开始后接模板形参列表组成,模板形参不能为空,一但声明了类模板就可以用类模板的形参名
 声明类中的成员变量和成员函数,即可以在类中使用内置类型的地方都可以使用模板形参名来声明
 eg:
 template<class T>
 class A
 {public: T a; T b;
 T hy(T c, T &d);};

 2.2 类模板对象的创建:比如一个模板类A,则使用类模板创建对象的方法为A<int> m;在类A后面跟上一个<>尖括号并在里面填上相应的
 类型,这样的话类A中凡是用到模板形参的地方都会被int 所代替。
 2.3 对于类模板,模板形参的类型必须在类名后的尖括号中明确指定。比如A<2> m;用这种方法把模板形参设置为int是错误的。

 2.4 在类模板外部定义成员函数的方法为:
 template<模板形参列表>
 函数返回类型 类名<模板形参名>::函数名(参数列表){函数体},
 eg:比如有两个模板形参T1,T2的类A中含有一个void h()函数,则定义该函数的语法为:
 template<class T1,class T2>
 void A<T1,T2>::h(){}。

 2.5 再次提醒注意:模板的声明或定义只能在【全局,命名空间或类范围内】进行。即不能在局部范围,函数内进行,
 比如不能在main函数中声明或定义一个模板。
 */


// 三、模板的形参
/* 有三种类型的模板形参:类型形参,非类型形参和模板形参。
 * 3.1
 *
 */

5. 多态

  1. // override限定符可以用来检查父类中是否有同名虚函数并且该函数是否可以被覆盖

【总结】 利用纯虚函数 virtual int area() = 0; // 纯虚函数 实现多态,通过调用函数的对象类型(这个就是不同的方法)来执行不同的方法。

【菜鸟c++】

多态按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。

C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数

下面的实例中,基类 Shape 被派生为两个类,如下所示:

#include <iostream> 
using namespace std;
 
class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      int area()
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};
class Rectangle: public Shape{
   public:
      Rectangle( int a=0, int b=0):Shape(a, b) { }
      int area () override
      { 
         cout << "Rectangle class area :" <<endl;
         return (width * height); 
      }
};
class Triangle: public Shape{
   public:
      Triangle( int a=0, int b=0):Shape(a, b) { }
      int area () override
      { 
         cout << "Triangle class area :" <<endl;
         return (width * height / 2); 
      }
};
// 程序的主函数
int main( )
{
   Shape *shape;
   Rectangle rec(10,7);
   Triangle  tri(10,5);
 
   // 存储矩形的地址
   shape = &rec;
   // 调用矩形的求面积函数 area
   shape->area();
 
   // 存储三角形的地址
   shape = &tri;
   // 调用三角形的求面积函数 area
   shape->area();
   
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Parent class area :
Parent class area :

导致错误输出的原因是,调用函数 area() 被编译器设置为基类中的版本,这就是所谓的静态多态,或静态链接 - 函数调用在程序执行前就准备好了。有时候这也被称为早绑定,因为 area() 函数在程序编译期间就已经设置好了。

但现在,让我们对程序稍作修改,在 Shape 类中,area() 的声明前放置关键字 virtual,如下所示:

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      virtual int area() // 虚函数-继承关联
      {
         cout << "Parent class area :" <<endl;
         return 0;
      }
};

修改后,当编译和执行前面的实例代码时,它会产生以下结果:

Rectangle class area :
Triangle class area :

此时,编译器看的是指针的内容,而不是它的类型。因此,由于 tri 和 rec 类的对象的地址存储在 *shape 中,所以会调用各自的 area() 函数。

正如您所看到的,每个子类都有一个函数 area() 的独立实现。这就是多态的一般使用方式。有了多态,您可以有多个不同的类,都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。

这样就方便了,我可以随时更换不同的方法,但输入和输出是一样的。我只需要通过指针指向不同的方法即可


虚函数:【动态链接-后期绑定】

虚函数 是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数

我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定


纯虚函数

您可能想要在基类中定义虚函数,以便在派生类中重新定义该函数更好地适用于对象,但是您在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。

我们可以把基类中的虚函数 area() 改写如下:

class Shape {
   protected:
      int width, height;
   public:
      Shape( int a=0, int b=0)
      {
         width = a;
         height = b;
      }
      // pure virtual function
      virtual int area() = 0; // 纯虚函数
};

= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数

6. 命名空间

假设这样一种情况,当一个班上有两个名叫 Zara 的学生时,为了明确区分它们,我们在使用名字之外,不得不使用一些额外的信息,比如他们的家庭住址,或者他们父母的名字等等。

同样的情况也出现在 C++ 应用程序中。例如,您可能会写一个名为 xyz() 的函数,在另一个可用的库中也存在一个相同的函数 xyz()。这样,编译器就无法判断您所使用的是哪一个 xyz() 函数。

因此,引入了命名空间这个概念,专门用于解决上面的问题,它可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。

[eg:]我们举一个计算机系统中的例子,一个文件夹(目录)中可以包含多个文件夹,每个文件夹中不能有相同的文件名,但不同文件夹中的文件可以重名。

定义命名空间

命名空间的定义使用关键字 namespace,后跟命名空间的名称,如下所示:

namespace namespace_name {
   // 代码声明
}

为了调用带有命名空间的函数或变量,需要在前面加上命名空间的名称,如下所示:

name::code;  // code 可以是变量或函数

让我们来看看命名空间如何为变量或函数等实体定义范围

【eg:】

#include <iostream>
using namespace std;
 
// 第一个命名空间
namespace first_space{
   void func(){
      cout << "Inside first_space" << endl;
   }
}
// 第二个命名空间
namespace second_space{
   void func(){
      cout << "Inside second_space" << endl;
   }
}
int main ()
{
 
   // 调用第一个命名空间中的函数
   first_space::func();
   
   // 调用第二个命名空间中的函数
   second_space::func(); 
 
   return 0;
}

当上面的代码被编译和执行时,它会产生下列结果:

Inside first_space
Inside second_space
using 指令 参考

您可以使用 using namespace 指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。

不连续的命名空间

命名空间可以定义在几个不同的部分中,因此命名空间是由几个单独定义的部分组成的。一个命名空间的各个组成部分可以分散在多个文件中。

所以,如果命名空间中的某个组成部分需要请求定义在另一个文件中的名称,则仍然需要声明该名称。下面的命名空间定义可以是定义一个新的命名空间,也可以是为已有的命名空间增加新的元素:

namespace namespace_name {
   // 代码声明
}
嵌套的命名空间

命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:

namespace namespace_name1 {
   // 代码声明
   namespace namespace_name2 {
      // 代码声明
   }
}

您可以通过使用 :: 运算符来访问嵌套的命名空间中的成员:

// 访问 namespace_name2 中的成员 
using namespace namespace_name1::namespace_name2; 

 // 访问 namespace:name1 中的成员 
using namespace namespace_name1;

在上面的语句中,如果使用的是 namespace_name1,那么在该范围内

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值