一: 命名空间域
二: 缺省参数
1:全缺省参数
2:半缺省参数
三:函数重载
1:重载
2:为什么C++支持函数重载,而C语言不支持函数重载?
3:如何通过函数的声明去寻找函数的地址呢?
4:C++调用C的库,C调用C++库
四:引用
1:做参数(引用传参)
2:做返回值
(1):引用返回
(2):传值返回
3:const与&应用
4:指针和引用的区别
五:内联函数总概
(1)宏
(2)内联函数
六:C++NULL与nullptr的区别
1:命名空间域:
使不同的域中可以定义两个名字相同的变量,或者函数,函数默认从局部域,全局域找,从命名空间域找需要指定.
命名空间域可以嵌套,一般在变量名称前加上 命名空间名 ::(调用函数时直接加上域名::哈函数名())
当然命名空间域还可以嵌套使用.
C++将c语言标准库和C++里面的东西都放到std中,防止我们创建的变量名与c语言库冲突;但是要用到std命名域中的函数必须在它前面加 命名空间域名 + ::
写上using namespace std;可以将std库里面的内容全部展开,相当于全局变量(使这个命名空间域的作用消失)
此时也可能会发生命名冲突,但是平时项目时可以将常用的变量展开:
using std:: 使用的函数名;
二:缺省参数
1:全缺省参数:
不能跳过a传给b;需要一个个从左到右传:(他不给形参的化,编译器默任使用函数声明所赋给的值)
2: 半缺省参数:
必须从左到右进行连续缺省。(否则编译器不给通过)
缺省参数不能再函数声明与定义中同时出现,当声明与定义分离时,规定给声明缺省参数。
三:函数重载:用的时候很方便,就像用同一个函数却实用了不同功能。
1:重载:
C++在同一作用域中声明几个功能类似的同名函数,这些函数的形参(参数个数,类型,顺序不同);
返回值不同不能构成函数重载:(返回值不同,调用时无法区分):
在函数重载构成时,系统会按照函数参数的不同来判断调用的函数。
2: 为什么C++支持函数重载,而C语言不支持函数重载?
编译器编译链接的过程:
预处理:头文件展开,宏替换,条件编译,去掉注释,形成预处理文件.c
编译: 语法检查,生成汇编代码,形成预处理文件.i(函数栈帧的机器码)
在编译时,执行到函数调用时有例如call func()指令,函数定义编译时通过jump指令就能得到函数的地址(jump第一条指令的地址)。(也就是说要有函数的定义才能得到函数的地址)。
汇编:将汇编代码转换成二进制代码,形成.s
链接:形成.o文件,还需要找一些只给函数声明的函数地址(有对应的函数的定义通过编译器的编译,才能找到函数的地址)
3:那如何通过函数的声明去寻找函数的地址呢?
C++为了能够支持函数重载而有了一个机制: 函数名修饰规则。(函数的参数不同,函数修饰出来的名字也就不同)。
编译时每个函数定义.o文件会生成对的符号应表(里面包含了函数的地址以及经过函数名修饰规则的函数名)
在编译时调用这两个函数时 Call 的是经过函数名修饰规则修饰过的新的函数名。
链接时便会寻找一些只给函数声明的变量地址,通过函数名直接去对应的符号表去找。
就可以把函数定义生成的.o文件合并在一起了。(如果找不到就会产生链接错误).
但是对于C语言来说,他并没有函数名修饰规则,函数名相同在编译时就无法分辨去生成对应的符号表。(此时会造成语法错误)。
C++如何调用C的库?
1:先添加现有项并配置成静态库,并重新生成,在相关文件中有了.lib文件就说明生成成功。
找到打所需要的头文件声明(并通过相对路径来找到声明)
设置附加库目录:此路径位.lib文件目录路径
添加依赖项(填写编译生成的.lib文件)
此时依旧无法编译完成:因为CPP的头文件展开编译用的是函数名修饰规则,它在调用此函数时函数名为经过C++名字修饰规则修饰过的,而c语言在符号表中的函数名并未经过函数名修饰规则修饰的,所以它们的函数名不同,所要寻找在对应符号表中的函数地址也就难以找到。
所以:我们可以在C++程序中的函数声明加入extern "C",此时可以让函数声明以C++的修饰方式来修饰,此时便可以使链接的函数名对应上了。
c语言如何调用C++库?
将callc换成换成.c文件,将静态库.c文件调成.cpp文件(记得要重新运行)
将C++库使用条件编译按照c语言的方式链接。
引用:
使用场景:
1:做参数 ---------------------------- r1,r2是a,b的临时拷贝。(大对象传参可以提高效率)
不用引用:----------------------------r1,r2是a,b的别名。
经过typedef 将strucct SListNode 定义成指针类型的*PSLTNode,指针list传参时用同该类型的引用接收PSLTNode& phead;
2:作返回值:
传值返回:
返回值为n的拷贝。
有static时,n在销毁前便会保存在栈里面的静态区中
为什么要那n的拷贝作为返回值?
在返回函数前一般先销毁栈帧,如果不拷贝变量那么n就会被销毁变成随机值,返回也变成了随机值。
如果变量小,那么在销毁时就会拷贝保存在寄存器中。
如果变量大,在销毁前便会就会提前在main函数拷贝,作为返回值。
总的来说,为了不出其他问题,只要是传值传参,返回的是变量的拷贝。
当我们在函数中使用引用时,在返回n的引用时n就被销毁了。当第二次返回调用时,就发生了越界访问,n就变成了随机值。
#include <iostream>
using namespace std;
int& Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int& ret = Count();
printf("%d\n", ret);
printf("%d\n", ret);
return 0;
}
因为static 修饰局部变量时,该变量称为静态局部变量,静态局部变量的存储空间在静态存储区,与函数的堆栈区不在同一个区域,因此函数运行完毕后静态局部变量不会被释放。
所以当返回n的别名时,n并没有被操作系统给销毁,所以可以采用引用传参。
#include <iostream>
using namespace std;
int& Count()
{
static int n = 0;
n++;
return n;
}
int main()
{
int& ret = Count();
printf("%d\n", ret);
printf("%d\n", ret);
return 0;
}
引用传参,引用返回的应用场景:
因为单链表的数组是动态开辟的,它不受函数栈帧的影响,所以可以用引用返回。
在调用这个函数时,可以通过函数的返回值的引用,来找到这个函数所要寻找的数。
缺省参数当函数声明与定义分开时可以写在它的声明处。
stdio.h头文件
#include<iostream>
#include<assert.h>
using namespace std;
struct Stack
{
int* a;
int top;
int capacity;
};
// 缺省参数不能在函数声明和定义中同时出现
// 分离定义时:声明给缺省参数
void StackInit(struct Stack* ps, int capacity = 4);
typedef struct SeqList
{
int* a;
int size;
int capacity;
}SL;
void SLInit(SL& s, int capacity = 4);
void SLPushBack(SL& s, int x);
// 修改顺序数据的函数
//void SLModity(SL& s, int pos, int x);
int& SLAt(SL& s, int pos);
Stack.cpp文件
#include "Stack.h"
void StackInit(struct Stack* ps, int capacity)
{
ps->a = (int*)malloc(sizeof(int) * capacity);
// ...
ps->top = 0;
ps->capacity = capacity;
}
void SLInit(SL& s, int capacity)
{
s.a = (int*)malloc(sizeof(int) * capacity);
assert(s.a);
// ...
s.size = 0;
s.capacity = capacity;
}
void SLPushBack(SL& s, int x)
{
if (s.size == s.capacity)
{
// ...
}
s.a[s.size++] = x;
}
int& SLAt(SL& s, int pos) //此时在栈中,不受函数栈帧的影响。
{
assert(pos >= 0 && pos <= s.size);
return s.a[pos]; //返回的数组的一个数的别名。
}
test.cpp文件
#include <iostream>
using namespace std;
#include "Stack.h"
int main()
{
SL sl;
SLInit(sl); //一次性开创需要的空间避免重复开创空间。
SLPushBack(sl, 1);
SLPushBack(sl, 2);
SLPushBack(sl, 3);
SLPushBack(sl, 4);
for (int i = 0; i < sl.size; ++i)
{
cout << SLAt(sl, i) << endl;
};
return 0;
}
3:const 与 &应用
1:在函数定义类型前加入const会使函数的权限变小。(从右相对于左边看):
2: 不同的类型赋值或者引用会发生隐式类型转换,此时便会产生一个权限较小的,并且类型与左边相同的变量),而引用时实际是取得是这个变量的别名。
此时rdd不可以引用的原因就是临时变量比rdd权限小,相当与权限增大了。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
const int& c = a;
const int& c = b;
int& b = c; //c的权限比b的大。
int ii = 1;
double& rdd = ii; //不可以。
const double& rdd = ii; //在类型前面加const; //可以。
return 0;
}
当我们引用传参时而不改变这个数时,为了避免传参时权限问题,尽量加上const;
#include <iostream>
using namespace std;
void func1( int& n )
{
;
}
int main()
{
int a = 10;
const int b = 20;
func1(a); //可以传参;
func(b); //不可以传参,权限变大了。
}
4:指针与引用的区别:
从语法角度而言:引用没有开空间,指针开创了4or8个空间(32位4,64位8)。
从底层实现而言: 引用时实际是由于指针来实现的。(它们在底层的实现是相同的)。
从功能而言: 指针更加强大,更加复杂,有着更大的不确定性。
引用更安全,更简单。
五:内联函数
1:宏
宏的优点: a:可维护性强 b:宏函数可以提高效率,宏替换可以减少函数栈帧的建立。
缺点: a: 可读性差。 b:不方便调试。c:没有类型安全检查。
#include <iostream>
using namespace std;
#define Add(a,b) ((a)+(b))
int main()
{
Add(1, 2) * 3; //防止四则运算搞混运算顺序。
int x = 1;
int y = 2;
Add(x | y, x & y);//|,&&优先级小于+,-。防止y与x先加减。
return 0;
}
2:内联函数:
inline修饰的函数叫做内联函数,编译时C++会调用函数内联的地方展开来减少函数压栈的开销,
内联函数可以提升程序运行的效率。
当编译代码过长时(递归,循环)他就不i展开,当编译代码较短时,他就会展开。(具体还要看编译器的设置)
没有展开。
当内联函数的声明预定义分开放置在不同的头文件时,编译器在编译时会通过函数的声明去寻找在符号表里面去寻找对应函数的地址。但是在编译链接时,编译器已经默认它为内联函数会展开,便不会将它的地址放入到.obj文件中。
六:NULL与null:
在指针为空时,使用null调用函数却会出现函数调用错误的问题,为了避免这些错误,在编写代码时最好采用nullptr来表示就空指针。
#include <iostream>
using namespace std;
#include"Stack.h"
void f(int) {
cout << "f(int)" << endl;
}
void f(int*) {
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL); //我们原本想调用第二个,然而编译器却调用了第一个。
f((int*)NULL); //调用了第二个;
return 0;
}
使用nullptr效果如下: