一.命名空间
1.域的访问顺序
(1)局部域,全局域,命名空间域(使用了域作用限定符才会进行访问)
(2)域作用限定符
【1】访问全局变量:在函数内部,如果局部变量与全局变量同名,局部变量的作用域会覆盖全局变量的作用域。此时,如果要访问全局变量,就需要使用“::”操作符。
【2】访问命名空间成员:命名空间是C++中用于组织代码的一种方式,可以避免全局命名冲突。在访问命名空间中的成员时,可以使用“::”操作符来指定命名空间。
【3】访问类静态成员:类的静态成员(包括静态变量和静态成员函数)属于类本身,而不是类的某个具体对象。因此,在访问这些静态成员时,可以使用类名和作用域限定符“::”。
#include<iostream>
int globalVar = 5; // 全局变量
namespace MyNamespace
{
int namespaceVar = 10; // 命名空间中的变量
}
class MyClass
{
public:
static int staticVar; // 类的静态变量
int instanceVar; // 类的非静态变量
static void staticFunc() //静态成员函数
{
std::cout << "Static function called." << std::endl;
}
void instanceFunc() //非静态成员函数
{
std::cout << "Instance function called." << std::endl;
}
};
int MyClass::staticVar = 20; // 静态变量初始化
int main()
{
MyClass obj;
obj.instanceVar = 30; // 访问类的非静态变量
std::cout << ::globalVar << std::endl; // 使用::访问全局变量,输出5
std::cout << MyNamespace::namespaceVar << std::endl; // 访问命名空间中的变量,输出10
std::cout << MyClass::staticVar << std::endl; // 访问类的静态变量,输出20
// 下面的调用会导致编译错误,因为非静态成员依赖于具体对象
// std::cout << MyClass::instanceVar << std::endl;
MyClass::staticFunc(); // 调用类的静态函数
obj.instanceFunc(); // 调用类的非静态函数
return 0;
}
2.命名空间的定义和使用
在C/C++中,变量、函数和类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多命名冲突。使用命名空间的目的是对标识符的名称进行封装,避免命名冲突或者名字污染,namespace关键字的出现就是针对这种问题的。
(1)命名空间的定义
定义命名空间,需要使用namespace关键字,后面跟命名空间名字,然后接上{},{}
中即为命名空间的成员。
(2)命名空间的使用
【1】命名空间当中可以定义变量、函数、类型
#include<iostream>
namespace dai
{
int rand = 10;//定义变量
int Add(int left, int right)//定义函数
{
return left + right;
}
struct node//定义类型
{
struct Node* next;
int val;
};
}
【2】命名空间可以嵌套
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
【3】相同的命名空间可以合并
#include<iostream>
//test.cpp
namespace N1
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N2
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
//test.h
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
//一个工程中的test.h和上面test.cpp中两个N1会被合并成一个
//因此要注意避免合并命名空间的时候出现命名冲突的问题
3.命名空间的展开
(1)加命名空间名称及作用域限定符
#include<iostream>
namespace N
{
int a = 5;
int b = 40;
}
int main()
{
printf("%d", N:: a);
return 0;
}
(2)使用using将命名空间中某个成员引入(命名空间的部分展开)
#include<iostream>
namespace N
{
int a = 5;
int b = 40;
}
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
(3)使用using namespace 命名空间名称引入(命名空间的全部展开)
#include<iostream>
namespace N
{
int a = 5;
int b = 40;
}
using namespace N;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
二.缺省参数
1.缺省参数含义和作用
(1)缺省参数的含义
缺省参数(也称为默认参数)是指在函数声明或定义中,为某些或全部参数指定的默认值。
(2)缺省参数的作用
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。(在不传实际参数用默认的参数即缺省参数,也可以显示传参)
2.缺省参数的分类
(1)全缺省参数:每一个参数都给出缺省值
(2)半缺省参数:从右往左给出缺省参数,但不是全缺省
半缺省参数的注意事项:
半缺省参数必须从右往左给值,函数调用必须从左往右给实参值,不能间隔给值(3)
3.缺省参数的应用
#include<iostream>
using namespace std;
struct Stack
{
int* a;
int top;
int capacity;
};
void StackInit(struct Stack* pst, int defaultCapacity = 4)
//调用没传参,默认开辟空间的大小
{
pst->a = (int*)malloc(sizeof(int) * defaultCapacity);
if (pst->a == NULL)
{
perror("malloc fail");
return;
}
pst->top = 0;
pst->capacity = defaultCapacity;
}
int main()
{
struct Stack st1;
StackInit(&st1);
//使用缺省值
StackInit(&st1, 100);
// 插入100个数据
return 0;
}
4.函数的声明和定义不能同时给缺省参数
当函数的声明和定义分开时,缺省值只能在函数声明的时候给
原因:编译器编译链接时候先处理函数声明,避免因为声明和定义缺省值不一样而产生的错误
三.函数重载
1.函数重载的含义
函数重载是一种允许在同一个作用域内创建多个同名函数的方法,但这些同名函数的参数列表(参数的数量或类型)必须不同。编译器根据传递给函数的参数的数量和类型来区分这些同名函数,从而决定调用哪一个函数。
2.函数重载的条件
(1)函数名相同:重载的函数必须具有相同的函数名
(2)参数列表不同:重载的函数的参数列表必须不同。这可以通过改变参数的数量或类型来实现。
(3)返回类型可以相同,也可以不同:虽然返回类型不影响函数重载的区分,但通常建议为重载的函数提供不同的返回类型,以提高代码的可读性和可维护性。然而,仅通过返回类型不同来重载函数是不合法的,因为编译器在调用函数时不会考虑返回类型。
3.为什么c++支持函数重载,c语言不支持
(1)c语言的函数名修饰规则
C语言在编译时,对函数名的修饰相对简单,通常只是在函数名前加上一个下划线作为前缀,或者根据调用约定在函数名前后增加特定的字符。
由于这种修饰规则没有考虑到函数的参数类型和数量,因此当存在同名但参数不同的函数时,编译器无法区分它们,从而导致链接错误。
(2)c++的函数名修饰规则
C++引入了函数签名的概念,函数签名包括函数名和参数列表。
在C++中,编译器会根据函数的参数类型和数量对函数名进行复杂的修饰(以确保每个函数都有唯一的标识符。
这种修饰规则使得C++能够支持函数重载,因为即使函数名相同,只要参数列表不同,编译器就能生成不同的函数标识符,从而正确区分和调用不同的函数版本。
四.引用
1.引用的含义
(1)引用是一种复合数据类型,创建一个变量来代表另一个已经存在的变量,并且这两个名字将指向同一个内存位置。引用不是新定义一个变量,而是给已存在变量取了一个别名
(2)引用书写格式
int a = 0;
int& b = a;
int& c = b;
2.引用的特性
(1)引用在定义时必须被初始化。
(2)一个变量可以有多个引用,但是引用一旦被初始化,就不能再改变为引用另一个变量。
(3)引用本身不占用额外的存储空间,它只是提供了一个访问现有变量的新途径。
3.引用的注意事项
(1)引用用const修饰的时候,会有权限的放大缩小和平移
int main()
{
const int a = 0;
int& b = a;//写法错误,变量的权限被放大
int c = a;//这是赋值
const int d = a;//权限的平移
int m = 0;
const int& n = m;//权限的缩小
return 0;
}
(2)临时变量具有常量的性质(临时变量具有常性)
4.引用的使用场景(作为参数和返回值)
(1)引用作为参数
【1】作为输出型参数,当要对实参进行修改时,引用作为参数更加方便
(2)引用作为返回值
【1】一定不能返回局部变量的引用
【2】引用作为返回值,使得返回值具有读写能力
可以获取函数返回值,又可以修改函数返回值
(3)任何场景都可以使用引用作为参数,但是使用引用作为返回值需要谨慎。引用作为参数和返回值都可以减少拷贝,提高效率,节省空间。
5.引用和指针的区别
(1)引用概念上定义一个变量的别名,指针存储一个变量地址。
(2)引用在定义时必须初始化,指针没有要求
(3)引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
(4)没有NULL引用,但有NULL指针
(5)在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节64位平台下占8个字节)
(6)引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
(7)有多级指针,但是没有多级引用
(8)访问实体方式不同,指针需要显式解引用,引用编译器自己处理
(9)引用比指针使用起来相对更安全
五.范围for
1.范围for的含义和写法
(1)范围for循环(也称为基于范围的for循环)是一种简洁的迭代容器(如数组等)中元素的语法。它允许你遍历容器中的所有元素,而无需使用传统的迭代器或索引。
(2)对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
for (declaration : range)
{
// 循环体
}
• declaration:这是循环中使用的变量的声明。对于每个迭代之后,
这个变量都会被初始化为range中的下一个元素。
• range:这是要迭代的容器或范围。
它可以是数组、向量、列表、集合、映射等支持迭代的容器。
2.数组或者容器的遍历
遍历数组
#include <iostream>
int main()
{
int arr[] = {1, 2, 3, 4, 5};
for (int num : arr)
{
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
输出:
1 2 3 4 5
3.范围for的使用条件
(1)for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供
begin和end的方法,begin和end就是for循环迭代的范围。
(2)当使用范围for循环遍历容器时,你不能直接修改容器的大小(例如,不能向向量中添加或删除元素)。但是,你可以修改容器中的元素(如果它们不是const的)。
(3)迭代的对象要实现++和==的操作。范围for循环背后的机制是使用迭代器来遍历容器。但是,你不需要显式地使用迭代器,因为编译器会为你处理这些细节。
六.自动识别类型
1.auto的含义
C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。auto关键字用于自动类型推断,它允许编译器根据初始化表达式自动推断变量的类型。
2.auto的基本用法和使用规则
(1)自动类型推断
int a = 10;
auto b = a; // b的类型被推断为int
auto c = 3.14; // c的类型被推断为double
(2)和指针与引用结合使用
int x = 10;
auto a=&x;// a是一个int指针
auto* ptr = &x; // ptr是一个int指针
auto& ref = x; // ref是x的引用,类型为int&
const auto& cref = x; // cref是x的常量引用,类型为const int&
(3)用于简便复杂的数据类型
std::map<int, std::string> myMap = {{1, "one"}, {2, "two"}};
auto it = myMap.begin(); // it的类型被推断为std::map<int, std::string>::iterator
(4)在同一行定义多个变量
void test()
{
auto a = 1, b = 2;
auto c = 3, d = 4.05//该代码会编译失败
}
(5)auto结合范围for
std::vector<int> vec = {1, 2, 3, 4, 5};
for (auto elem : vec)
{
std::cout << elem << std::endl;
}
// elem的类型被推断为vec中元素的类型,即int
(6)auto不能作为函数的形参
(7)auto不能直接用来声明数组

七.内联函数
1.内联函数的含义
(1)以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
(2)用建立栈帧的开销,内联函数提升程序运行的效率。
(3)内联函数是一种建议编译器将函数体直接插入到每个调用该函数的地方的优化手段。
这样做的目的是减少函数调用的开销,特别是对于那些体积小、调用频繁的函数。
(4)然而,需要注意的是,内联只是一种向编译器提出的建议,编译器可以选择忽略这个建议,尤其是在函数体较大或包含复杂逻辑时。
(5)inline是一种以空间换时间的做法,如果编译器将函数当成内联函数处理,在编译阶段,会
用函数体替换函数调用,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
2.内联函数的注意事项
(1)inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。