1. :: 作用域运算符

通常情况下,如果有两个同名变量,一个是全局变量,另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,它将屏蔽全局变量。

  1. //全局变量
  2. int a = 10;
  3. void test(){
  4. //局部变量
  5. int a = 20;
  6. //全局a被隐藏
  7. cout << "a:" << a << endl;
  8. }

程序的输出结果是a:20。在test函数的输出语句中,使用的变量a是test函数内定义的局部变量,因此输出的结果为局部变量a的值。

作用域运算符可以用来解决局部变量与全局变量的重名问题

  1. int atk = 1000;
  2. void test01()
  3. {
  4. int atk = 2000;
  5. std::cout << "atk = " << atk << std::endl;
  6. // ::代表作用域 如果前面什么都不添加 代表全局作用域
  7. std::cout << "全局 atk = " << ::atk << std::endl;
  8. }

image.png
作用域运算符可以用来解决局部变量与全局变量的重名问题,即在局部变量的作用域内,可用::对被屏蔽的同名的全局变量进行访问。

2. 名字控制

2.1 C++命名空间(namespace)

在c++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突性的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免,在大规模程序的设计中,以及在程序员使用各种各样的C++库时,这些标识符的命名发生冲突,标准C++引入关键字namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。

2.2 命名空间使用语法

  1. 命名空间用途: 解决名称冲突

    1. void test01()
    2. {
    3. KingGlory::goAtk();
    4. LOL::goAtk();
    5. }
  2. 命名空间下 可以放 变量、函数、结构体、类…

    1. namespace A
    2. {
    3. int m_A;
    4. void func();
    5. struct Person
    6. {};
    7. class Animal
    8. {};
    9. }
  3. 命名空间 必须要声明在全局作用域下

    1. void test02()
    2. {
    3. //namespace B{}; 不可以命名到局部作用域
    4. }
  4. 命名空间可以嵌套命名空间

    1. namespace B
    2. {
    3. int m_A = 10;
    4. namespace C
    5. {
    6. int m_A = 20;
    7. }
    8. }
    9. void test03()
    10. {
    11. cout << "B空间下的m_A = " << B::m_A << endl;
    12. cout << "C空间下的m_A = " << B::C::m_A << endl;
    13. }

    image.png

  5. 命名空间是开放的,可以随时给命名空间添加新的成员 ```cpp namespace B { int m_A = 10; namespace C {

    1. int m_A = 20;

    } }

namespace B { int m_B = 100; } void test04() { cout << “B空间下的m_A = “ << B::m_A << endl; cout << “B空间下的m_B = “ << B::m_B << endl; }

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22788275/1647876169127-69419e00-0831-46e8-8256-c80e44a4ff70.png#clientId=uebdca8a6-897d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=80&id=ubc860603&margin=%5Bobject%20Object%5D&name=image.png&originHeight=120&originWidth=463&originalType=binary&ratio=1&rotation=0&showTitle=false&size=12136&status=done&style=none&taskId=u67d52559-2ce3-4e00-9c38-c6ec533209a&title=&width=308.6666666666667)
  2. 6. 名空间可以是匿名的
  3. ```cpp
  4. namespace
  5. {
  6. int m_C = 1000;
  7. int m_D = 2000;
  8. // 当写的命名空间的匿名的,相当于写了
  9. // static int m_C = 1000; static int m_D = 2000;
  10. }
  11. void test05()
  12. {
  13. cout << "m_C = " << m_C << endl;
  14. cout << "m_D = " << ::m_D << endl;
  15. }

image.png

  1. 命名空间可以起别名 ```cpp namespace veryLongName { int m_E = 10000; void func() {
    1. cout << "aaa" << endl;
    } } void test06() { namespace veryShortName = veryLongName; cout << veryShortName::m_E << endl; cout << veryLongName::m_E << endl;

}

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/22788275/1647876257685-1f65d63b-31e2-4b36-8c6d-3d3df086277f.png#clientId=uebdca8a6-897d-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=91&id=ufba9d597&margin=%5Bobject%20Object%5D&name=image.png&originHeight=129&originWidth=399&originalType=binary&ratio=1&rotation=0&showTitle=false&size=8621&status=done&style=none&taskId=ud8d69bfe-364c-4312-a78f-a83dff58451&title=&width=281)
  2. <a name="SmJEp"></a>
  3. ### 2.3 using声明
  4. 1. using声明
  5. ```cpp
  6. namespace KingGlory
  7. {
  8. int sunwukongId = 1;
  9. }
  10. void test01()
  11. {
  12. int sunwukongId = 2;
  13. //1、using声明
  14. //using KingGlory::sunwukongId ;
  15. //当using声明与 就近原则同时出现,出错,尽量避免
  16. cout << sunwukongId << endl;
  17. // 2
  18. }
  1. using声明碰到函数重载
    1. namespace A {
    2. void func() {}
    3. void func(int x) {}
    4. void func(int x, int y) {}
    5. }
    6. void test() {
    7. using A::func;
    8. func();
    9. func(10);
    10. func(10, 20);
    11. }
    如果命名空间包含一组用相同名字重载的函数,using声明就声明了这个重载函数的所有集合。

2.4 using编译指令

  1. namespace KingGlory
  2. {
  3. int sunwukongId = 1;
  4. }
  5. namespace LOL
  6. {
  7. int sunwukongId = 3;
  8. }
  9. void test02()
  10. {
  11. //int sunwukongId = 2;
  12. //2、using编译指令
  13. using namespace KingGlory;
  14. using namespace LOL;
  15. //当using编译指令 与 就近原则同时出现,优先使用就近
  16. //当using编译指令有多个,需要加作用域 区分
  17. cout << KingGlory::sunwukongId << endl;
  18. cout << LOL::sunwukongId << endl;
  19. }
  20. /*
  21. 1
  22. 3
  23. */

注意:使用using声明或using编译指令会增加命名冲突的可能性。也就是说,如果有名称空间,并在代码中使用作用域解析运算符,则不会出现二义性。

当引入一个全局的using编译指令时,就为该文件打开了该命名空间,它不会影响任何其他的文件,所以可以在每一个实现文件中调整对命名空间的控制。比如,如果发现某一个实现文件中有太多的using指令而产生的命名冲突,就要对该文件做个简单的改变,通过明确的限定或者using声明来消除名字冲突,这样不需要修改其他的实现文件。

3. C++增强于C的地方

3.1 全局变量检测增强

  1. //1、全局变量检测增强
  2. int a;
  3. int a = 10;
  1. //1、全局变量检测增强 C++检测出重定义
  2. int a;
  3. //int a = 10;

3.2 C++中所有的变量和函数都必须有类型

返回值、形参类型、函数调用参数个数都要检查

  1. //2、函数检测增强 返回值没有检测 形参类型没有检测 函数调用参数个数没有检测
  2. getRectS( w , h)
  3. {
  4. return w *h;
  5. }
  6. void test01()
  7. {
  8. printf("%d\n", getRectS(10, 10, 10));
  9. }
  1. //2、函数检测增强 返回值检测、形参类型检测、函数调用参数个数
  2. int getRectS(int w,int h)
  3. {
  4. return w *h;
  5. }
  6. void test01()
  7. {
  8. printf("%d\n", getRectS(10, 10));
  9. }

3.3 更严格的类型转换

在C++,不同类型的变量一般是不能直接赋值的,需要相应的强转。

  1. void test02()
  2. {
  3. char * p = malloc(64);
  4. }
  1. //3、类型转换检测增强
  2. void test02()
  3. {
  4. char * p = (char *)malloc(64);
  5. }

3.4 struct类型加强

  1. C中定义结构体变量需要加上struct关键字,c++不需要。
  2. C中的结构体只能定义成员变量,不能定义成员函数。c++即可以定义成员变量,也可以定义成员函数。 ```c //4、struct增强 struct Person { int age; //void func(); C语言下 结构体不可以有函数 };

void test03() { struct Person p; //创建结构体变量时候,必须加关键字struct p.age = 100; }

  1. ```cpp
  2. //4、struct增强 C++可以放函数,创建结构体变量,可以简化关键字 struct
  3. struct Person
  4. {
  5. int age;
  6. void func()
  7. {
  8. age++;
  9. }
  10. };
  11. void test03()
  12. {
  13. Person p;
  14. p.age = 17;
  15. p.func();
  16. cout << "p的age = " << p.age << endl;
  17. }

3.5 三目运算符功能增强

c语言三目运算表达式返回值为数据值,为右值,不能赋值

  1. //6、三目运算符增强
  2. void test04()
  3. {
  4. //?:
  5. int a = 10;
  6. int b = 20;
  7. printf("ret = %d\n", a > b ? a : b);
  8. *(a > b ? &a : &b) = 100; //C语言下 返回的是右值 20 = 100
  9. printf("a = %d\n", a);
  10. printf("b = %d\n", b);
  11. }
  1. //6、三目运算符增强
  2. void test05()
  3. {
  4. //?:
  5. int a = 10;
  6. int b = 20;
  7. printf("ret = %d\n", a > b ? a : b);
  8. (a < b ? a : b )= 100; // C++下返回的是变量 b = 100
  9. printf("a = %d\n", a);
  10. printf("b = %d\n", b);
  11. }

4 bool 类型关键字

标准c++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。这三个名字都是关键字。

  1. bool类型只有两个值,true(1值),false(0值)
  2. bool类型占1个字节大小
  3. 给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换false(0)
    1. /5bool类型扩展 C语言下 没有这个类型
    2. //bool a;
    ```cpp //5、bool类型扩展 C语言下 没有这个类型 C++有bool类型 bool flag = true; // bool类型 代表 真和假 true —— 真(1) false —— 假(0)

void test04() { cout << sizeof(bool) << endl; //结果是1个字节 //flag = false; //flag = 100; //将非0的数都转为1 cout << flag << endl; }

  1. <a name="K75c0"></a>
  2. ## 5 C/C++中的const
  3. <a name="TLyUi"></a>
  4. ### 5.1 const概述
  5. const单词字面意思为常数,不变的。它是c/c++中的一个关键字,是一个限定符,它用来限定一个变量不允许改变,它将一个对象转换成一个常量。
  6. ```cpp
  7. const int a = 10;
  8. A = 100; //编译错误,const是一个常量,不可修改

5.2 C/C++中const的区别

5.2.1 C中的const

常量的引进是在c++早期版本中,当时标准C规范正在制定。那时,尽管C委员会决定在C中引入const,但是,他们c中的const理解为”一个不能改变的普通变量”,也就是认为const应该是一个只读变量,既然是变量那么就会给const分配内存,并且在c中const是一个全局只读变量,c语言中const修饰的只读变量是外部连接的。

  1. const int arrSize = 10;
  2. int arr[arrSize];

看似是一件合理的编码,但是这将得出一个错误。 因为arrSize占用某块内存,所以C编译器不知道它在编译时的值是多少

5.2.2 C++中的const

在c++中,一个const不必创建内存空间,而在C中,一个const总是需要一块内存空间。在c++中,是否为const常量分配内存空间依赖于如何使用。一般说来,如果一个const仅仅用来把一个名字用一个值代替(就像使用#define一样),那么该存储局空间就不必创建。
如果存储空间没有分配内存的话,在进行完数据类型检查后,为了代码更加有效,值也许会折叠到代码中。
不过,取一个const地址, 或者把它定义为extern,则会为该const创建内存空间。
在c++中,出现在所有函数之外的const作用于整个文件(也就是说它在该文件外不可见),默认为内部连接,c++中其他的标识符一般默认为外部连接。

5.2.3 C/C++中const异同总结

  1. c语言全局const会被存储到只读数据段。c++中全局const当声明extern或者对变量取地址时,编译器会分配存储地址,变量存储在只读数据段。两个都受到了只读数据段的保护,不可修改。 ```c const int constA = 10;

int main() { int p = (int)&constA; *p = 200; }

  1. 以上代码在c/c++中编译通过,在运行期,修改constA的值时,发生写入错误。原因是修改只读数据段的数据。
  2. 2. c语言中局部const存储在堆栈区,只是不能通过变量直接修改const只读变量的值,但是可以跳过编译器的检查,通过指针间接修改const值。
  3. ```c
  4. const int constA = 10;
  5. int* p = (int*)&constA;
  6. *p = 300;
  7. printf("constA:%d\n",constA);
  8. printf("*p:%d\n", *p);
  9. /*
  10. constA:300
  11. *p:300
  12. */

c语言中,通过指针间接赋值修改了constA的值。

c++中对于局部的const变量要区别对待:

  1. 对于基础数据类型,也就是const int a = 10这种,编译器会把它放到符号表中,不分配内存,当对其取地址时,会分配内存。 ```cpp const int constA = 10; int p = (int)&constA; p = 300; cout << “constA:” << constA << endl; cout << “p:” << *p << endl;

/运行结果: constA:10 p:300 */

  1. constA在符号表中,当我们对constA取地址,这个时候为constA分配了新的空间,*p操作的是分配的空间,而constA是从符号表获得的值。
  2. 2. 对于基础数据类型,如果用一个变量初始化const变量,如果`const int a = b`,那么也是会给a分配内存。
  3. ```cpp
  4. int b = 10;
  5. const int constA = b;
  6. int* p = (int*)&constA;
  7. *p = 300;
  8. cout << "constA:" << constA << endl;
  9. cout << "*p:" << *p << endl;
  10. /*运行结果:
  11. constA:300
  12. *p:300
  13. */

constA 分配了内存,所以我们可以修改constA内存中的值。

  1. 对于自定数据类型,比如类对象,那么也会分配内存。 ```cpp const Person person; //未初始化age //person.age = 50; //不可修改 Person pPerson = (Person)&person; //指针间接修改 pPerson->age = 100; cout << “pPerson->age:” << pPerson->age << endl; pPerson->age = 200; cout << “pPerson->age:” << pPerson->age << endl;

/运行结果: pPerson->age:100 pPerson->age:200 /

  1. person分配了内存,所以我们可以通过指针的间接赋值修改person对象。
  2. **Cconst默认为外部连接,c++中const默认为内部连接**.当c语言两个文件中都有`const int a`的时候,编译器会报重定义的错误。而在c++中,则不会,因为c++中的const默认是内部连接的。如果想让c++中的const具有外部连接,必须显示声明为: `extern const int a = 10`;
  3. ![](https://cdn.nlark.com/yuque/0/2022/jpeg/22788275/1648095685294-7b90084c-c136-43e4-b80b-95e5c90aeefc.jpeg)
  4. <a name="rMKq6"></a>
  5. ### 5.3 尽量以const替换#define
  6. 在旧版本C中,如果想建立一个常量,必须使用预处理器”<br />`#define MAX 1024;`<br />我们定义的宏MAX从未被编译器看到过,因为在预处理阶段,所有的MAX已经被替换为了1024,于是MAX并没有将其加入到符号表中。但我们使用这个常量获得一个编译错误信息时,可能会带来一些困惑,因为这个信息可能会提到1024,但是并没有提到MAX.如果MAX被定义在一个不是你写的头文件中,你可能并不知道1024代表什么,也许解决这个问题要花费很长时间。
  7. 解决办法就是用一个常量替换上面的宏。<br />`const int max= 1024;`
  8. `const``#define`区别总结:
  9. 1. const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
  10. 1. const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
  11. <a name="emFqv"></a>
  12. ## 6. new操作符
  13. C++中利用**new**操作符在堆区开辟数据<br />堆区开辟的数据,由程序员手动开辟,手动释放,释放利用操作符**delete**
  14. 语法:`ElementType* VariableName = new ElementType([InitialValue])`<br />数组:`ElementType* ArrayName = new ElementType[ArrayLength]`
  15. 删除释放数组时语法为:`delete[] ArrayName`<br />利用new创建的数据,会返回该数据对应的类型的指针
  16. **示例1 基本语法**
  17. ```cpp
  18. #include<iostream>
  19. using namespace std;
  20. int* func()
  21. {
  22. int* a = new int(10);
  23. return a;
  24. }
  25. int main() {
  26. int* p = func();
  27. cout << *p << endl;
  28. cout << *p << endl;
  29. //利用delete释放堆区数据
  30. delete p;
  31. //cout << *p << endl; //报错,释放的空间不可访问
  32. return 0;
  33. }

运行结果:
image.png

示例2:开辟数组

  1. #include<iostream>
  2. using namespace std;
  3. //堆区开辟数组
  4. int main() {
  5. int* arr = new int[10];
  6. //为数组赋值
  7. for (int i = 0; i < 10; i++)
  8. {
  9. arr[i] = i + 100;
  10. }
  11. //输出数组里的值
  12. for (int i = 0; i < 10; i++)
  13. {
  14. cout << arr[i] << endl;
  15. }
  16. //释放数组 delete 后加 []
  17. delete[] arr;
  18. return 0;
  19. }

运行结果:
image.png