『C/C++养成计划』C++中的双冒号::名解析(Scope Resolution Operator)

Posted 布衣小张

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了『C/C++养成计划』C++中的双冒号::名解析(Scope Resolution Operator)相关的知识,希望对你有一定的参考价值。

C++中的双冒号::名解析(Scope Resolution Operator)!

文章目录

  • C++中的双冒号名解析(Scope Resolution Operator),也称作作用域运算符,用于指明一个标识符的作用域。双冒号一般用于以下几种情况:

1. 访问命名空间中的成员

  • 在C++中,命名空间是将全局作用域分隔为更小的区域,以避免命名冲突的一种机制。可以使用双冒号来访问命名空间中的成员,例如:
namespace ns 
    int a;
    void foo() 


int main() 
    ns::a = 1;  // 使用双冒号访问命名空间中的 a
    ns::foo();  // 使用双冒号访问命名空间中的 foo 函数

2. 访问类中的静态成员

  • 在 C++ 中,可以使用类名和双冒号来访问类中的静态成员,例如:
// C++ program to show that :: can be used to access static 
// members when there is a local variable with same name 
#include<iostream> 
using namespace std; 
 
class Test 
 
    static int x;   
public: 
    static int y;    
 
    // Local parameter 'a' hides class member 
    // 'a', but we can access it using :: 
    void func(int x)   
      
       // We can access class's static variable 
       // even if there is a local variable 
       cout << "Value of static x is " << Test::x; 
 
       cout << "\\nValue of local x is " << x;   
     
; 
 
// In C++, static members must be explicitly defined  
// like this 
int Test::x = 1; 
int Test::y = 2; 
 
int main() 
 
    Test obj; 
    int x = 3 ; 
    obj.func(x); 
 
    cout << "\\nTest::y = " << Test::y; 
 
    return 0; 
 

3. 嵌套类访问

  • 如果另一个类中存在一个类,我们可以使用嵌套类使用作用域运算符来引用嵌套的类,例如:
// Use of scope resolution class inside another class.
#include <iostream>
using namespace std;

class outside 
public:
    int x;
    class inside 
    public:
        int x;
        static int y;
        int foo();
    ;
;
int outside::inside::y = 5;

int main() 
    outside A;
    outside::inside B;

  • 下面这种情况和下面std::vector<int>::iterator的情况一样。
// Use of scope resolution class inside another class.
#include <iostream>
using namespace std;

class A 
public:
    A()  cout << "constructor" << endl; 
;

class B 
public:
    typedef A B_A;
;

int main(int argc, char const *argv[]) 
    B::B_A a;
    return 0;

// constructor
  • c++中的标准库vector中,std::vector<int>::iterator it; 给出解释:在这个例子中,std是标准命名空间,vector是std中的一个类,int是vector类的模板参数之一,iterator是vector类中的一个类型定义(type definition)。因此,std::vector::iterator表示vector类的迭代器类型,它的别名为iterator。
  • 也就是说:当一个类内部定义了一个嵌套类型(nested type),比如 std::vector 内部定义了 iterator 类型,我们可以使用 :: 来访问这个类型。
  • 源码中iterator是这样定义的,typedef __gnu_cxx::__normal_iterator<pointer, vector> iterator;这里的 __gnu_cxx::__normal_iterator 实际上是一个迭代器类,用于在容器中遍历元素。pointer 表示指向元素的指针类型,而 vector 则表示容器类型。
  • typedef 是 C++ 中的一个关键字,用于给一个已有的数据类型起一个新的名称。它的作用是让程序员可以用一个简短、易懂的名字来代替一个复杂、冗长的数据类型名称,从而提高程序的可读性和可维护性。
# typedef <原类型> <新类型名>;
typedef int myInt;
myInt a = 5;
  • 在 C++11 标准中,也可以使用 using 来定义类型别名,如下,这样定义的效果与上述 typedef 的效果相同。
using myInt = int;
#include <iostream>
#include <vector>

int main() 
    std::vector<int> vec = 1, 2, 3, 4, 5;
    std::vector<int>::iterator it = vec.begin(); // 使用 :: 访问嵌套类型 iterator
    std::cout << *it << std::endl; // 输出 1
    return 0;


4. 在类之外定义函数

// C++ program to show that scope resolution operator :: is used 
// to define a function outside a class 
#include<iostream>  
using namespace std; 
 
class A  
 
public:  
 
   // Only declaration 
   void fun(); 
; 
 
// Definition outside class using :: 
void A::fun() 
 
   cout << "fun() called"; 
 
 
int main() 
 
   A a; 
   a.fun(); 
   return 0; 
 

5. 当存在具有相同名称的局部变量时,要访问全局变量

// C++ program to show that we can access a global variable 
// using scope resolution operator :: when there is a local  
// variable with same name  
#include<iostream>  
using namespace std; 
 
int x;  // Global x 
 
int main() 
 
  int x = 10; // Local x 
  cout << "Value of global x is " << ::x; 
  cout << "\\nValue of local x is " << x;   
  return 0; 
 

6. C++模板参数的自动推导


7. C++类成员函数后面加const

  • 如果类的成员函数后面加了const关键字,说明这个函数是不能改变类中的成员变量的。 如果在编写该函数会修改类中的成员变量,编译时会出错,并且也提高了程序的可读性,当我们看到函数后面有const的话就知道这个函数是不会修改类中数据的。mutable (mjuːtəb(ə))意思是可变的。

8. C++类构造函数后前加explicit(ɪkˈsplɪsɪt显式的)

  • C++中的explicit关键字 只能用于修饰只有一个参数的类构造函数, 它的作用是表明该构造函数是显示的,而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。
  • 那么显示声明的构造函数和隐式声明的有什么区别呢? 我们来看下面的例子:
#include <cstring>
#include <iostream>

class CString // 没有使用explicit关键字的类声明, 即默认为隐式声明

public:
    CString(int size)
    
        _size = size;                     // string的预设大小
        _pstr = (char *)malloc(size + 1); // 分配string的内存
        memset(_pstr, 0, size + 1);
    
    CString(const char *p)
    
        int size = strlen(p);
        _pstr = (char *)malloc(size + 1); // 分配string的内存
        strcpy(_pstr, p);                 // 复制字符串
        _size = strlen(_pstr);
    
    // 析构函数这里不讨论, 省略...
protected:
    char *_pstr;
    int _size;
;

int main(int argc, char const *argv[])

    CString string1(24);  // 这样是OK的, 为CString预分配24字节的大小的内存
    CString string2 = 10; // 这样是OK的, 为CString预分配10字节的大小的内存
    // CString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CString”: 没有合适的默认构造函数可用
    CString string4("aaaa"); // 这样是OK的
    CString string5 = "bbb"; // 这样也是OK的, 调用的是CString(const char *p)
    CString string6 = 'c';   // 这样也是OK的, 其实调用的是CString(int size), 且size等于'c'的ascii码
    string1 = 2;             // 这样也是OK的, 为CString预分配2字节的大小的内存
    string2 = 3;             // 这样也是OK的, 为CString预分配3字节的大小的内存
    // string3 = string1;       // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用

    return 0;


  • 上面的代码中, CxString string2 = 10; 这句为什么是可以的呢?
  • 在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 “CxString string2 = 10;” 这段代码, 编译器自动将整型转换为CString类对象, 实际上等同于下面的操作:
CxString string2(10);  
// 或
CxString temp(10);  
CxString string2 = temp;  
  • 但是, 上面的代码中的_size代表的是字符串内存分配的大小, 那么调用的第二句 “CxString string2 = 10;” 和第六句 “CxString string6 = ‘c’;” 就显得不伦不类, 而且容易让人疑惑. 有什么办法阻止这种用法呢? 答案就是使用explicit关键字. 我们把上面的代码修改一下, 如下:
  • explicit关键字的作用就是防止类构造函数的隐式自动转换


9. C++函数指针和指针函数

  • 首先我们能先来看这两种概念的写法:
  • 函数指针:int (*f) (int x,int y); 函数指针本质是一个指针,其指向一个函数。
  • 指针函数:int *f (int x,int y); 指针函数本质是一个函数,其返回值为指针。

  • 函数指针:
#include <stdio.h>
#include <stdlib.h>

int add(int x, int y)

    return x + y;

int sub(int x, int y)


    return x - y;

/*这是一个函数指针,因为()的优先级大于*的有限级,但是(*f)排在前边,所示这是一个指针,
函数指针就是一个指针,他和变量指针一样,指向的都是一个地址,例如变量指针(也就是我们常说的指针)指向的是这个变量的地址
而函数指针,指向的是这个函数的地址
*/
int (*f)(int x, int y); // 定义了一个函数指针
int main()

    f = &add;                        // 因为f是一个函数指针,也就是一个指针,那么就是一个地址,现在取函数add的地址赋给f,也就是函数f的功能等同于函数add的功能
    printf("1+2= %d\\n", (*f)(1, 2)); 
    f = add;               			// 这里&add,add都可以都是首地址         
    printf("1+2= %d\\n", (*f)(1, 2));

    f = &sub;
    printf("1-2= %d\\n", (*f)(1, 2));
    f = sub;
    printf("1-2= %d\\n", (*f)(1, 2));


// 1 + 2 = 3 
// 1 - 2 = -1
  • 指针函数:
#include <iostream>

typedef struct 
    int a;
    int b;
 Data;

// 指针函数
Data *f(int a, int b)

    Data *data = new Data;
    data->a = a;
    data->b = b;
    return data;


int main(int argc, const char *argv[])

    Data *myData = f(4, 5);
    std::cout << "f(4,5)= " << myData->a << myData->b;

    return 0;


10. C++类模板例子(简单队列)

  • my_queue.h:
#include <cstdlib>

// 空队列异常类
class EQueueEmpty ;

// 队列类项前置声明
template <typename T>
class JuQueueItem;

/// @brief 队列类模板, 这里我们使用一个单向链表保存队列中的元素
/// @tparam T
template <typename T>
class JuQueue 
public:
    JuQueue() : _head(NULL), _tail(NULL) 
    virtual ~JuQueue();
    virtual void Enter(const T &item);             // 队尾入队
    virtual T Leave();                             // 对头出队
    bool IsEmpty() const  return _head == NULL;  // 队列是否为空

private:
    JuQueueItem<T> *_head, *_tail; // 头指针和尾指针, 指向队列的第一个元素和最后一个元素。
;

/// @brief 队列项类模板的定义,单向链表结构
/// @tparam T
template <typename T>
class JuQueueItem 
    friend class JuQueue<T>; // 友元类,JuQueue<T>就是JuQueueltem<T>的友元, 你存了这种型的数据, 那么那个队列本身就是它的友元, 以方便它访问这里边结点中的数据

public:
    JuQueueItem(const T &item) : _item(item), next(NULL) 

private:
    T _item;
    JuQueueItem<T> *next;
;

  • my_queue.cpp:
#include "my_queue.h"
#include <iostream>

// 队列类模板析构函数
template <typename T>
JuQueue<T>::~JuQueue() 
    while (!IsEmpty()) 
        Leave(); // 队列非空的时候,一个接着一个地删除队列中的全部的元素
    


// 入队
template <typename T>
void JuQueue<T>::Enter(const T &item)            // 把项插入到队列中,插入到队尾
    JuQueueItem<T> *p = new JuQueueItem<T>(item); // 定义一个指针, 指向JuQueueItem<T>这样的型, new一个对象。
    if (IsEmpty()) 
        _head = _tail = p;
     else 
        _tail->next = p;
        _tail = p;
    


// 出队
template <typename T>
T JuQueue<T>::Leave() 
    if (IsEmpty())
        throw EQueueEmpty();
    JuQueueItem<T> *p = _head; // 找到对头元素
    T _retval = p->_item;      // 要出队列的元素
    _head = _head->next;       //
    delete p;                  // 销毁队列的首节点
    return _retval;


int main() 
    // 第一种方式int类型
    JuQueue<int> *p = new JuQueue<int>;
    for (int i = 0; i < 10; i++) 
        p->Enter(i);
    
    while (p != NULL)
        std::cout << p->Leave() << std::endl;

    // 第二种整数的地址放在队列里, 也就是说我存的不再是那个整数
    /*
    对于整数来讲 意义好像不是那么大, 但是如果你是用这个队列来存储一个复杂的数据结构, 那么存指针显然要比存数据要经济得多, 至少不需要频繁的拷贝和赋值
     */
    int *r = new int(10), *q = new int(20);
    JuQueue<int *> *t = new JuQueue<int *>;
    t->Enter(r);
    t->Enter(q);
    int *s = t->Leave();
    std::cout << *s << std::endl;

    return 0;


11. C++智能指针(memory头文件)

  • 智能指针有好几种:shared_ptr,unique_ptr,weak_ptr
  • shared_ptr:共享对象的所有权,但性能略差。
  • unique_ptr:独占对象的所有权,由于没有引用计数,因此性能较好
  • weak_ptr:配合shared_ptr,解决循环引用的问题
  • shared_ptr共享指针:会记录有多少个共享指针指向同一个物体,当这个记录数字降为0的时候,程序会自动释放这个物体,省去我们手动delete的烦恼。shared_ptr的这个方法就叫引用计数,顾名思义引用计数的意思就是数一数有多少个共享指针指向某个物体

#include <iostream>
#include <memory>

int main(int argc, char const *argv[])

    std::shared_ptr<int> p1;
    p1 = std::make_shared<int>(100);       // 方法1: make_shared初始化, 更推荐使用这一种, 因为make_shared的效率更高, c++17之前的编译器更安全
    std::shared_ptr<int> p2new int(100); // 方法2: 使用new初始化
    std::cout << *p2 << std::endl;
    return 0;


#include <iostream>
#include <memory>

/

Java 中的双冒号“::”

一、双冒号“::”就是 Java 中的方法引用(Method references)

方法引用的格式是类名::方法名。一般是用作 Lambda表达式

形如 ClassName::methodName 或者 objectName::methodName 的表达式,叫做方法引用(Method Reference)。看看编译器是如何根据 “晦涩难懂” 的 Method Reference 来推断开发者的意图的。例如:

1.表达式:
person -> person.getName();
可以替换成:
Person::getName

2.表达式:
() -> new HashMap<>();
可以替换成:
HashMap::new

二、四种方法引用

1️⃣指向静态方法的引用
2️⃣指向某个对象的实例方法的引用
3️⃣指向某个类型的实例方法的引用
4️⃣指向构造方法的引用

三、总结

其实,JVM 本身并不支持指向方法引用,过去不支持,现在也不支持。Java 8 对方法引用的支持只是编译器层面的支持,虚拟机执行引擎并不了解方法引用。编译器遇到方法引用的时候,会像上面那样自动推断出开发者的意图,将方法引用还原成接口实现对象,或者更形象地说,就是把方法引用设法包装成一个接口实现对象,这样虚拟机就可以无差别地执行字节码文件而不需要管什么是方法引用了。

需要注意的是,方法引用是用来简化接口实现代码的,并且凡是能够用方法引用来简化的接口,都有这样的特征:有且只有一个待实现的方法。这种接口在 Java 中有个专门的名称: 函数式接口。当试图用方法引用替代一个非函数式接口时,会有这样的错误提示: xxx is not a functional interface。

以上是关于『C/C++养成计划』C++中的双冒号::名解析(Scope Resolution Operator)的主要内容,如果未能解决你的问题,请参考以下文章

Shell脚本里的双冒号是什么意思

R中的双冒号(::)是啥?

Pig中的双冒号到底是啥意思?

(TA养成计划C语言篇)新的开始,谈谈怎样学习

“::before”和“:before”中的双冒号和单冒号的区别

java :: Java中的双冒号操作符