一、函数的区别
函数默认值参数
在函数声明或者定义的时候,给定参数默认值,如果实参传递时候,不给该形参传值,则会按照默认值传参
函数参数的默认值是在编译期生成指令的时候,直接生成入参指令。
函数的默认值参数需要从右往左依次赋值,不能跳过。
函数的值参数在同一作用域只能赋值一次,不能重复赋值。
因为函数参数的默认值是在编译期带入的,所以函数的参数的默认值只在本文件生效。
内联函数
在调用内联函数时,该函数会直接在调用点展开(在编译期展开指令)。
作用域只在本文件。
正常函数调用:
1.传参 2. 将下一行指令地址入栈 3.将ebp 寄存器入栈,esp ebp 偏移 ——开辟栈帧 4.执行函数体 5.返回值 6.栈帧回退 7.恢复现场 8.参数清除
在debug版本下,内联函数和正常函数调用方式一致。
在realse版本下,在调用内联函数时,该函数会在调用点展开。
递归函数一定不能作为内联函数。(由于内联在编译期展开,编译期无法获取变量的值,而递归函数的终止条件一定需要有变量参与,所以,递归函数不可能被处理成内联函数)
inline 只是对系统的建议(做不做是系统的事),建议将其函数处理为内联函数。
符号:
所有的数据都会生成符号
指令中只有函数名会生成符号
符号:
1.全局符号 global 符号(所有的文件只要引用声明就可以使用)
2.局部符号 local 符号(只有本文件可以用)
inline 产生的符号是 local 符号,所以只能在本文件下使用。
函数重载
函数名相同,参数列表不同
c 语言函数编译生成函数符号 ,依赖函数名
c++中函数编译生成的函数符号 , 依赖函数名+参数列表
bool compare(int a,int b)//c语言中生成的符号大致为 _compare
{
return a>b;
}
bool compare(char a,char b)//c++中生成的符号大致为_compare_char_char
{
return a>b;
}
类型的自动转换
普通函数
有类型安全校验(参数列表需要类型的都需要)
可以调试
会生成global 符号
静态函数
有类型安全校验
可以调试
会生成 local 符号
宏函数(替换)
没有类型安全校验
不可以调试
不生成符号
内联函数
有类型安全校验
可以调试(在debug 内联函数和静态函数表现一致(不会展开),在 release 版本下和宏函数表现一致)
在debug版本会生成local 符号,在release 版本不生成符号
二、c++与c语言之间的相互调用
1>如何用c++去调用c语言的代码
extern "C"
{
void fun_c(int a,int b);//将括号中的按照c语言标准编译
}
2>如何用c语言去调用c++的代码
1.可以直接在c++所需代码处加上extern "C",让其按照c语言标准编译就可链接上
#include<iostream>//c++
using namespace std;
extern "C"
{
void fun(int a, int b)
{
cout << "fun()" << endl;
}
}
但是若没有c++源代码,这种方法就不可行
2.若没有c++源代码,没有定义,只有函数声明时,可借助一个新的.cpp 文件
自己写一个.cpp 文件
void fun(int a, int b);//想要调用的c++函数的声明
extern "C"//括号中的代码产生c语言链接
{
void fun_tmp(int a, int b)//借助第三方函数,此函数的返回值,参数列表应与想调用的函数相同
{
return fun(a, b);//return 想调用的函数
}
}
在c中调用自己所写的函数
#include<stdio.h>
void fun_tmp(int a, int b);//通过调用自己所写函数,来达到调用所需函数的目的
int main()
{
fun_tmp(10, 20);
return 0;
}
三、指针、地址、数组名三者之间的区别
c++中三者一样
c语言中,数组名就是地址。
指针存放地址。(联系)
指针是个变量,地址是常量。(区别)
指针有类型。
#include<iostream>
using namespace std;
int main()
{
int a = 10;
int b = 20;
b = a;
b = 10;
}
四、命名空间
在C++中支持三种域:局部域、名字空间域和类域。
名字空间域是随标准C++而引入的。它相当于一个更加灵活的文件域(全局域),可以用花括号把文件的一部分括起来,并以关键字namespace开头给它起一个名字:
namespace将类型名,重命名放在一个空间之中,对外封闭。可以防止命名冲突
直接使用会报错。
若想使用,可以using namespace,整个命名空间里的东西都可以使用
若想只使用其中的一个,可以using Type::INT; 只使用命名空间Type中的INT,其他就不可以使用
花括号括起来的部分称声明块。声明块中可以包括:类、变量(带有初始化)、函数(带有定义)等。最外层的名字空间域称为全局名字空间域(global namespace scope),即文件域。
名字空间域的引入,主要是为了解决全局名字空间污染(global namespace pollution)问题,即防止程序中的全局实体名与其他程序中的全局实体名,命名冲突。
命名空间创建
//普通的命名空间
namespace zyt
{
int a_max = 10;
int g_min = 0;
int my_add(int a, int b) { return a + b; };
}
//名字空间域可分层嵌套,同样有分层屏蔽作用
namespace primer
{
double pi = 3.1415926;
double my_add(double a, double b) { return a + b; }
namespace Matrix
{
char my_max(char a, char b) { return a > b ? a : b; }
}
}
//同一个工程中允许存在多个相同名称的命名空间
//便一去最后会合成一个命名空间
namespace zyt
{
float pi = 3.14;
int my_sub(int a, int b)
{
g_min = a - b;
return g_min;
}
}
一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
名字空间使用
1.加名字空间及作用域限定符
int main()
{
int a = zyt::my_add(12, 23);//::作用域限定符
printf("%lf\n", primer::pi);
printf("%lf\n", zyt::pi);
primer::Matrix::my_max('a','b');
return 0;
}
2.使用using将名字空间中成员引入
使用using声明可只写一次限定修饰名。using声明以关键字using开头,后面是被限定修饰的名字空间成员名:
using zyt::pi;
using primer::Matrix::my_max;
//名字空间类成员matrix的using声明
// 以后在程序中使用matrix时,就可以直接使用该成员名,而不必使用限定修饰名
int main()
{
printf("%lf\n", primer::pi);
printf("%lf\n", pi);
my_max('a', 'b');
3.使用 using namespace 名字空间名称引入
使用using指示符可以一次性地使名字空间中所有成员都可以直接被使用,比using声明方便。using指示符;以关键字using开头,后面是关键字namespace,然后是名字空间名。
using namespace名字空间名;
using namespace zyt;
int main()
{
printf("%lf\n", primer::pi);
printf("%lf\n", pi);//zyt
my_add(12,23);
return 0;
}
//A.h
namespace zyt
{
int my_add(int, int);
int my_sub(int, int);
}
//A.cpp
#include"A.h"
namespace zyt
{
int my_add(int a, int b)
{
return a + b;
}
int my_sub(int a, int b)
{
return a - b;
}
}
使用using指示符
标准C++库中的所有组件都是在一个被称为std的名字空间中声明和定义的。在采用标准C++的平台上使用标准C++库中的组件,只要写一个using指示符:
using namespace std;
就可以直接使用标准C++库中的所有成员。这是很方便的。
五、动态开辟空间
c语言中使用malloc 和free
一维数组 二维数组
//给一个变量申请一个空间
int *p=(int *)malloc(sizeof(int));
free (p);
//一维数组的申请
int* p1 = (int*)malloc(10 * sizeof(int));
//一维数组的释放
free(p1);
//二维数组的申请arr[5][10]
int** p2 = (int **)malloc(5 * sizeof(int*));
for (int i = 0; i < 5; i++)
{
p2[i] =(int *) malloc(sizeof(int) * 10);
}
//二维数组的释放,先释放里层,再释放外层
for (int i = 0; i < 5; i++)
{
free(p2[i]);
}
free(p2);
c++ 中使用new 和 delete
//给变量申请一个空间
int* q = new int(10);//申请一个空间,并给q赋值为10
int* q = new int();//申请一个空间,并给q赋值为默认值,0
int* q = new int;//申请一个空间,并给q赋值为随机值
//释放
delete q;
//动态开辟一个10个元素大小的一维数组空间
int* q1 = new int[10];
//释放
delete[]q1;//释放数组要加上[],代表变量是一个数组
//二维数组的申请
int** q2 = new int* [5];
for (int i = 0; i < 5; i++)
{
q2[i] = new int[10];
}
//释放
for (int i = 0; i < 5; i++)
{
delete[]q2[i];
}
delete[]q2;
六、const
c语言中(.c),const 修饰的变量为常变量
c++中,const 定义的为常量,常量会在编译器将常量所在地方全部替换为该常量的值
const定义的为常量,是用const修饰,初始值为常量才可被称作常量
#include<iostream>
using namespace std;
int main()
{
const int a = 10;
int* p = (int *)&a;//强转
*p = 20;
cout << "a= " << a << endl;//10
cout << "*p= " << *p << endl;//20
}
若被const修饰,初始值为变量,则为常变量
也就是说,如果使用变量初始化常量,常量会退化为常变量
int c = 10;
const int a = c;//如果使用变量初始化常量,常量会退化为常变量
int* p = (int *)&a;//强转
*p = 20;
cout << "a= " << a << endl;//20
cout << "*p= " << *p << endl;//20
const修饰的量必须初始化,不初始化,后期就无法对其赋值。
七、引用
一个与指针密切相关的特殊数据类型。
底层是一个指针。所有使用到该引用的地方,在编译期会自动替换为底层指针的解引用。
引用是一个变量的别名,定义引用类型变量,实质上是给一个已定义的变量起一个别名。
引用类型变量与其相关的变量使用同一个内存空间。
定义引用类型变量的一般格式为:
类型符 & 引用名=变量名(& 为引用声明符)
如: int a=3;
int &b= a;
注意区分:引用声明运算符和取地址运算符
int *b= &a;
为什么引用必须初始化:所有使用到该引用的地方,在编译期会自动替换为底层指针的解引用。不初始化,后面就没机会了。
为什么一经引用就无法改变引用的目标:所有使用到该引用的地方,在编译期会自动替换为底层指针的解引用。
int &f ,引用底层是一个指针,但e是一个常量,引用一个常量,将常量的地址泄露给非常量的指针,定义一个常引用。
10会产生一个临时量,临时量都具有常属性
临时量的生命周期只在当前指令。
如果临时量被引用,他的生命周期就会扩大到和引用一致。
#include<iostream>
using namespace std;
void swap1(int* m, int* n)//指针
{
int tmp;
tmp = *m;
*m = *n;
*n = tmp;
}
void swap2(int &m, int &n)//引用
{
int tmp;
tmp = m;
m = n;
n = tmp;
}
int main()
{
int a = 10;
int b = 20;
swap1(&a, &b);//指针
cout << a << b << endl;//20 10
swap2(a, b);//引用
cout << a << b << endl;//10 20
}