目录
1.命名空间(namespace关键字)
2.C++输入&输出
3.缺省参数
4.函数重载
5.引用
5.1引用的概念
5.1引用的特性
5.3引用的使用场景
5.4传值、传引用效率比较
5.5常引用
5.6引用和指针的区别
6.extern "C"
7.内联函数
1.命名空间(namespace关键字)
在C/C++中,变量、函数、类等都是大量存在的,这些变量、函数和类的名称都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染。
1.错误代码:
#include <stdio.h>
#include <stdlib.h>
int rand = 0;
int main()
{
printf("%d",rand);
return 0;
}
这段代码会出现错误:主要原因是:在stdlib.h中存在rand函数,又定义了一个全局变量,会出现命名冲突。所以这样会出现错误或者警告。
2.修改错误代码:利用namespace关键字,
#include <stdio.h>
#include <stdlib.h>
namespace n1
{
int rand = 0;
}
int main()
{
printf("%d",n1::rand);
return 0;
}
3.命名空间定义
定义命名空间,需要使用到namesapce关键字,后面跟命名空间的名字,然后接一对{ }即可,{ }中
即为命名空间的成员。
普通的命名空间:
命名空间中的内容,可以是定义变量,也可以定义函数。
namespace N1 // N1为命名空间的名称
{
// 命名空间中的内容,既可以定义变量,也可以定义函数
int a;
int Add(int left, int right)
{
return left + right;
}
}
命名空间可以嵌套:
namespace N2
{
int a;
int b;
int Add(int left, int right)
{
return left + right;
}
namespace N3
{
int c;
int d;
int Sub(int left, int right)
{
return left - right;
}
}
}
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N1 // N1为命名空间的名称
{
int a;
int Add(int left, int right)
{
return left + right;
}
}
namespace N1
{
int Mul(int left, int right)
{
return left * right;
}
}
注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。
4.命名空间的使用:
对以下命名空间的使用有三种方式:
namespace N {
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
1.加命名空间名称及作用域限定符
只有结构体比较特殊需注意。
int main()
{
//变量的使用:
printf("%d\n", N::a);
//函数的使用:
N::Add(1, 2);
//结构体的使用
struct N::Node node;
return 0;
}
2.使用using将命名空间中成员引入
using N::a;
using N::Node;//注意结构体
int main()
{
printf("%d\n",a);
Node LT;
return 0;
}
3.使用using namespace 命名空间名称引入(这种方式需要慎用,这样就相当于命名空间失效,空间中所有定义的变量等都是全局)
using namespace N;
int main()
{
printf("%d\n",b);
printf("%d\n",a);
Node LT;
LT.val = 1;
return 0;
}
2.C++输入&输出
1.输出
#include <iostream>
using namespace std;
int main()
{
cout << "Hello world" << endl;
return 0;
}
需要包含头文件<iostream> ,需要打开std的空间,才能使用cout函数。<< 流插入这个符号可以看作是输出流(流向什么),cout为标准输出(控制台),把"Hello world"字符串流向控制台。endl相当于C语言中的 换行 \n?。>>流提取
注意:早期标准库将所有功能在全局域中实现,声明在.h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(V6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用<iostream>+std的方式。
2.输入:输入数据时以空格或回车为两个值的间隔,不会读取
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << c << endl;
return 0;
}
C++的输入输出与C语言的输入输出相比:输入和输出在使用时更加的方便,不需要增加数据格式控制,比如整形%d,字符%c.如果需要控制打印位数或者格式,可以使用printf,C++是支持C语言的,因此,在写代码时,可以写C++也可以写C语言,有点意思。即可以不用,但不排斥。很爽。那个方便就用那个。
3.缺省参数
1.什么是缺省参数
#include <iostream>
using namespace std;
void fun(int a=0)//缺省参数
{
cout << a << endl;
}
int main()
{
fun(1);//可以传参
fun();//不传参,打印缺省值
return 0;
}
结果:
?2.全缺省参数
#include <iostream>
using namespace std;
void fun(int a=10,int b=20,int c=30)//全缺省参数
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
int main()
{
//全缺省的传参
fun();
fun(1);
fun(1,2);
fun(1, 2,3);
return 0;
}
结果:
4.半缺省参数-缺省部分参数-必须从右向左缺省
void fun(int a,int b=20,int c=30)//半缺省参数
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
半缺省传参:
#include <iostream>
using namespace std;
void fun(int a,int b=20,int c=30)
{
cout << a << endl;
cout << b << endl;
cout << c << endl<<endl;
}
int main()
{
fun(1);
fun(1,2);
fun(1, 2,3);
return 0;
}
没有缺省的参数必须传参,缺省的参数可以传,也可以不传。?
注意:1.半缺省参数必须从右向左依次给出来,不能间隔给。
? ? ? ? ? ?2.给参数时是从左向右依次给。
? ? ? ? ? ?3.缺省参数不能再函数声明和定义中同时出现
text.cpp:
#include "test.h"
void StackInit(struct stack* ps, int capacity = 4)//给缺省参数
{
}
void StackPush(struct stack* ps, int x)
{
//
}
text.h:
#pragma once
#include <iostream>
void StackInit(struct stack* ps, int capacity = 4);
这样会出错,再函数声明和定义只能出现一个。?推荐写在声明中。
缺省参数在实际编程中的意义:
struct stack
{
int* a;
int top;
int capacity;
};
void StackInit(struct stack* ps, int capacity=4)//给缺省参数
{
}
void StackPush(struct stack* ps,int x)
{
}
int main()
{
struct stack st;
/*StackInit(&st)*/;//不知道多大就可以先用缺省值
StackInit(&st,20);//也可以自己给
return 0;
}
注意:缺省参数只能给常量或全局变量,C语言不支持。
4.函数重载
1.函数重载概念
函数重载:是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数。这些同名函数的形参列表(参数个数或类型或顺序)必须不同。
1.函数形参列表的类型不同
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
long Add(long left, long right)
{
return left + right;
}
int main()
{
Add(10, 20);
Add(10.0, 20.0);
Add(10L, 20L);
return 0; }
2.函数形参个数不同?
void fun()
{
}
void fun(int a)
{
}
3.函数形参顺序不同?
void fun(char a,int b)
{
}
void fun(int b,char a)
{
}
判断是否是重载,只有上面着三种情况。
注意:返回类型不同,是不能构成重载的。
short Add(short left, short right)
{
return left+right;
}
int Add(short left, short right)
{
return left+right;
}
对于这段代码:这两个形参的个数不同,是可以构成重载的,但是在调用时存在二义性。当不传参数时,会报错。要避免这种错误代码的出现。
void fun()
{
}
void fun(int a=0)
{
}
int main()
{
fun();//当不传参数的时候会报错。
fun(1);//传参时,可以
return 0;
}
*函数重载的原理:?
为什么C语言不支持,C++支持函数重载呢?
C++兼容C,在Liunx中,gcc 编译C语言,g++就是编译C++,VS中是后.c 还是.cpp。
C语言不支持函数重载,C++支持函数重载的原理:
fun.h:
#include <stdio.h>
void f();
void f(int a);
fun.c
#include "fun.c"
void f()
{
printf("f()\n");
}
void f(int a)
{
printf("f(int a)");
}
main.c
#include "fun.h"
int main()
{
f();
return 0;
}
编译连接的过程:
1.预编译:(预处理):头文件展开(fun.h文件就没有了),宏替换、条件编译、去掉注释。生成了 fun.i main.i。
2.编译:检查语法、生成汇编代码、汇总全局符号。生成:fun.s、main.s
3.汇编:把汇编代码转化成汇编指令(二进制机器指令),形成符号表。生成fun.o? main.o
4.链接:主要是完成.o文件的符号表的链接和符号表的重定位,重定位:就是把无效地址改成有效地址。
使用编译阶段的汇编代码分析问题:在编译和链接时出了问题,在预编译阶段展开头文件后,汇编阶段会向上寻找函数定义但是找到了函数声明(若在往上找时找到了定义,就直接把地址填上了。就不用链接是重定位了),此时执行main.o中的CALL f()机器指令的时侯f(地址)中地址是随机的或者无效的,汇编阶段形成的fun.o 二进制文件,并且形成了符号表,符号表内存储着函数名和地址,C语言在编译时不支持函数重载,因为编译的时候,两个函数的函数名相同,地址也就相同,在符号表内就有了冲突和歧义,在链接时,把main.o中的CALL f()机器指令f()的地址进行有效地址重定位(f(地址写入有效地址))时会发生两个函数地址一致,也存在歧义和冲突。而C++支持的原因是:C++目标文件中的符号表,不是直接用函数名来标识和查找函数。在Liunx中函数的修饰规则是:_Z是前缀,1是函数名长度,f函数名,v(void)类型首字符,函数修饰不一样,在汇编阶段fun.o的符号表地址不会冲突,链接的时候main.o的main的函数里面去调用两个重载的函数,就不会出现地址冲突。在C语言中的函数名修饰规则:只根据函数名进行修饰,因此不支持函数重载。?
5.引用
5.1引用的概念
引用不是新定义一个变量,而是给已存在变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
类型&引用变量名(对象名)=引用实体。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;//这就是引用,给变量a起了一个别名
return 0;
}
b是a的别名。修改b也会修改a。?
地址是一样的:
注意:引用类型必须和引用实体是同种类型。?
5.1引用的特性
1.引用在定义时必须初始化
若不定义会报错了:
2.一个变量可以有多个引用
给a去了一个名字叫b,给a取了一个名字叫c。
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b=a;
int& c = a;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
int& c = a;
int& d = c;//这一句还是相当于给a又起了一个别名
return 0;
}
3.引用一旦引用了一个实体,再不能引用其他实体
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int& b = a;
int c = 20;
b = c;//这句程序的意义是什么
return 0;
}
b=c 一共有两个意义:1.把c的值赋值给b 2.让b变成c的别名。然而这里的真正意义是:把c的值赋值给b,也就是修改了a的值。
5.3引用的使用场景
1.做参数
使用引用做参数
使用指针做参数
传值做参数
这三种都是可以构成函数重载的,对传引用的函数名解析:_Z4swapriri,指针:_Z4swappipi,传值:_Z4swapii,因为传值和传引用构成函数重载,但是在调用时会存在歧义,不swap(a,b)不知道调用传引用还是传值。?引用和指针实质都是再传地址
#include <iostream>
using namespace std;
void Swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a,b);//这里是存在歧义的。
printf("a=%d b=%d",a,b);
return 0;
}
?在链表中使用了二级指针:
void SeqListPushBack(SLTNode** pphead, SeqListData x);//尾插
#include <iostream>
#include "SList.h"
using namespace std;
int main()
{
SLTNode* plist = NULL;
SeqListPushBack(&plist, 1);
SeqListPushBack(&plist, 1);
return 0;
}
对指针起别名:我改变指针别名就改变了指针
#include <iostream>
#include "SList.h"
using namespace std;
int main()
{
int a = 10;
int& b=a;
int* p1 = &a;
//对指针起别名
int*& r1 = p1;
return 0;
}
对链表二级指针修改成引用:这样就可以不使用二级指针,在调用时也不需要传一级指针的地址。
void SeqListPushBack(SLTNode*& phead, SeqListData x);//尾插
//尾插
void SeqListPushBack(SLTNode*& phead, SeqListData x)
{
SLTNode* newnode = BuySeqListNode(x);
if (NULL == phead)
{
phead = newnode;
}
else
{
SLTNode* tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
#include <iostream>
#include "SList.h"
using namespace std;
int main()
{
SLTNode* plist = NULL;
SeqListPushBack(plist, 1);
SeqListPushBack(plist, 2);
SeqListPirnt(plist);
return 0;
}
有的书上还有这样改的:
typedef struct SeqList
{
SeqListData data;
struct SeqList *next;
}SLTNode,*List;//这里等价于struct SeqList* List
void SeqListPushBack(List& phead, SeqListData x);//尾插
//尾插
void SeqListPushBack(List& phead, SeqListData x)
{
SLTNode* newnode = BuySeqListNode(x);
if (NULL == phead)
{
phead = newnode;
}
else
{
SLTNode* tail = phead;
while (tail->next != NULL)
{
tail = tail->next;
}
tail->next = newnode;
}
}
在使用时还是一样的:其实这种写法和上面的一样。
#include <iostream>
#include "SList.h"
using namespace std;
int main()
{
SLTNode* plist = NULL;
SeqListPushBack(plist,1);
SeqListPushBack(plist,2);
SeqListPirnt(plist);
return 0;
}
2.引用做返回值
先分析传值返回:
#include <iostream>
using namespace std;
int add(int a,int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = add(1,2);
cout << ret << endl;
return 0;
}
其实这里返回值并不是c给了ret,而是c的拷贝,拷贝一个临时变量:
?那临时变量存在哪里?
1.如果c比较小(4或8个字节),一般是寄存器充当临时变量
注意:临时变量具有常性,只能做右值,不能修改。
2.如果c比较大,临时变量放在调用add函数的栈帧中。
把寄存器eax的值给了ret。
分析引用返回值:
引用返回不会生成c的拷贝返回值,而是直接返回c的别名。
#include <iostream>
using namespace std;
int& add(int a,int b)
{
int c = a + b;
return c;
}
int main()
{
int ret = add(1,2);
cout << ret << endl;
return 0;
}
?代码存在的问题:
1.这样是存在非法访问的,因为add(1,2)的返回值是c的引用,所以add函数栈帧销毁后,回去访问c的位置空间。这样的有可能会返回正确的值。编译器检查不出来
2.如果add函数栈帧销毁,清理了空间,那么c取值的时候就是随机值,给ret就是随机值,当前这个取决于编译器实现了。
因此以上代码是错误的。难道返回引用就没有用处了吗?
注意:如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经返还给系统,则就用传值返回。
int& test()
{
int* p =(int*)malloc(4);
return *p;//p也是局部变量,不能返回p的别名,返回那块堆区的空间
}
引用也可修改返回变量:
#define N 10
int& test(int i)
{
static int a[N];
return a[i];
}
int main()
{
//写
for (int i = 0; i < N; i++)
{
test(i) = 10 + i;//修改返回变量
}
//读
for (int i = 0; i < N; i++)
{
cout << test(i) << " " << endl;
}
return 0;
}
5.4传值、传引用效率比较
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
以下代码是用来测试传引用参数时的效率:
#include <iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
void TestFunc1(A a) {}
void TestFunc2(A& a) {}
void TestRefAndValue()
{
A a;
// 以值作为函数参数
size_t begin1 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc1(a);
size_t end1 = clock();
// 以引用作为函数参数
size_t begin2 = clock();
for (size_t i = 0; i < 10000; ++i)
TestFunc2(a);
size_t end2 = clock();
// 分别计算两个函数运行结束后的时间
cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main()
{
TestRefAndValue();
return 0;
}
结果:
以下代码是测试传引用返回值:
#include <iostream>
#include <time.h>
using namespace std;
struct A { int a[10000]; };
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
// 以值作为函数的返回值类型
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
// 以引用作为函数的返回值类型
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
// 计算两个函数运算完成之后的时间
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
}
int main()
{
TestReturnByRefOrValue();
return 0;
}
结果:?
很明显这两种最后都是传引用的效率非常的好,因为不管是传引用还是返回引用,都不要额外的建立临时变量,大大的提高了效率。
?总结一下:
1.引用的作用主要体现在传参和传返回值,在有些场景下(大对象或深拷贝对象),可以提高性能。2.引用传参和传返回值,输出型参数和输出型返回值。通俗点说,有些场景下,形参的改变可以改变实参
5.5常引用
1.
第一种情况引用权限的扩大,直接标红报错,其他两种引用是没有问题的。
2.分析问题
int main()
{
double d = 10;
int i = d;
double e = 10;
int& r1 = e;//为什么这里不行
const int& r2 = e;//这里就可以
return 0;
}
相加时的结果也会存在临时变量,也需要加const.建立临时变量的都是不能修改的,右值。因此需要注意。什么时候产生临时变量:运算完原数据如果没有被修改,运算时都是建立一个临时变量。
3.结论
const type& 可以接收各种类型的对象。
5.6引用和指针的区别
1.引用概念上定义一个变量的别名,指针存储一个变量地址。
2.引用在定义时必须初始化,指针没有要求。
3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向一个同类型的实体
4.没有NULL引用,但有NULL指针
5.在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位平台下占8个字节)
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。
7.访问实体方式不同,指针需要使用解引用,引用编译器自己处理
8.引用比指针使用起来相对更安全(指针存在越界,或者空指针问题),有多级指针,没有多级引用
总结:指针相对复杂一些,容易出错。
6.extern "C"
有些时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern"C",意思告诉编译器,将该函数按照C语言规则来编译。
这一小节主要:完成C++调用C的静态库,C调用C++的静态库,extern"C"发挥着重要的作用。
VS2019建立静态库项目的步骤:
1.
2.
1.使用C++调用C的静态库
在桌面上创建了一个c++的项目,和一个c语言的静态库?
这里用到了判断有效符号的程序:
我们写的程序main.cpp:
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
bool isValid(const char* s) {
Stack st;
StackInit(&st);
while (*s != '\0')
{
if (*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
}
else
{
//防止只有一个右括号
if (StackEmpty(&st))
{
StackDestory(&st);
return false;
}
STDataType temp = StackTop(&st);
StackPop(&st);
if ((temp == '(' && *s != ')') ||
(temp == '[' && *s != ']') ||
(temp == '{' && *s != '}'))
{
StackDestory(&st);
return false;
}
}
s++;
}
//若栈不为空,则就不匹配
if (!StackEmpty(&st))
{
StackDestory(&st);
return false;
}
StackDestory(&st);
return true;
}
int main()
{
cout << isValid("([{}])") << endl;
return 0;
}
这时我们需要调用C语言的静态库来完成程序的运行,这个静态库可以是别人写好的,也可以自己写好的,这里我用的是自己写的栈(程序在博客栈中写过了),我们把栈的程序弄到静态库项目中:源文件是.c就是C语言静态库,.cpp就是C++静态库
?编译完成后会生成.lib文件,静态库编译好后,在main.cpp中调用
#include "../../C语言静态库/C语言静态库/Stack.h"
../是从当前c++源文件目录向上一级,然后我们再向上一级../就到了桌面,我们把C语言静态库里面编译好的头文件包含好。然后编译会发现出现很多链接错误:
主要原因是:在用C++调用C语言静态库时,会用C++的函数名规则去修饰C语言的函数名规则。C++函数名的修饰规则和C语言的修饰规则本来就不同,所以会出现链接错误。
需要将C语言静态库利用C语言的函数名修饰规则来修饰,因此用到extern "C"{} ,括号里面的所有函数用C语言的函数名规则来修饰链接的。
extern "C"
{
#include "../../C语言静态库/C语言静态库/Stack.h"
}
但是发现还是会出现链接错误,引用完头文件还是不够的,需要进行VS配置:
?
要把这个静态库目录配置上 :Debug里面全是.lib文件
?在附加依赖项把.lib添加上,我这里的名字是C语言静态库.lib?最好用英文
?
配置好后在再进行编译,就可以了。?
运行结果和程序测试:
?
2.使用C调用C++静态库
还是一样,我们需要建立一个C++的静态库,然后编译好
在创建一个C语言项目工程,main.c:包含好头文件? #include "../../C++静态库/C++静态库/Stack.h"
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdbool.h>
#include "../../C++静态库/C++静态库/Stack.h"
bool isValid(const char* s) {
Stack st;
StackInit(&st);
while (*s != '\0')
{
if (*s == '(' || *s == '[' || *s == '{')
{
StackPush(&st, *s);
}
else
{
//防止只有一个右括号
if (StackEmpty(&st))
{
StackDestory(&st);
return false;
}
STDataType temp = StackTop(&st);
StackPop(&st);
if ((temp == '(' && *s != ')') ||
(temp == '[' && *s != ']') ||
(temp == '{' && *s != '}'))
{
StackDestory(&st);
return false;
}
}
s++;
}
//若栈不为空,则就不匹配
if (!StackEmpty(&st))
{
StackDestory(&st);
return false;
}
StackDestory(&st);
return true;
}
int main()
{
int ret = isValid("({[]})");
return 0;
}
编译发现链接错误,此时是因为使用C语言的函数名修饰规则,来解析C++库文件,修饰规则的不同所以会发生链接错误。也需要在VS中配置好属性里面的链接器的常规和输入,和上面一样。
我们需要去改静态库,因为只有C++认识extern "C"?
在C++静态库中:这样写还是不对,因为在C语言中会展开头文件,C语言不认识extern "C"?
extern "C"
{
void StackInit(Stack* ps);//栈初始化
void StackPush(Stack* ps, STDataType data);//入栈
void StackPop(Stack* ps);//出栈
STDataType StackTop(Stack* ps);//获取栈顶的数据
int StackSize(Stack* ps);//获取栈的有效元素个数
int StackEmpty(Stack* ps);//检测栈是否为空。空为1,不空为0
void StackDestory(Stack* ps);//栈销毁
void StackPrint(Stack* ps);//栈数据打印
}
修改方法:
1.
#ifdef __cplusplus //建立C++项目后这个标识符已经定义好
extern "C"
{
#endif
void StackInit(Stack* ps);//栈初始化
void StackPush(Stack* ps, STDataType data);//入栈
void StackPop(Stack* ps);//出栈
STDataType StackTop(Stack* ps);//获取栈顶的数据
int StackSize(Stack* ps);//获取栈的有效元素个数
int StackEmpty(Stack* ps);//检测栈是否为空。空为1,不空为0
void StackDestory(Stack* ps);//栈销毁
void StackPrint(Stack* ps);//栈数据打印
#ifdef __cplusplus
}
#endif
2.
#ifdef __cplusplus //建立C++项目后这个标识符已经定义好
#define EXTERN extern "C"
#else
#define EXTERN //定义为空,会被空格代替,预处理后为空格
#endif
EXTERN void StackInit(Stack* ps);//栈初始化
EXTERN void StackPush(Stack* ps, STDataType data);//入栈
EXTERN void StackPop(Stack* ps);//出栈
EXTERN STDataType StackTop(Stack* ps);//获取栈顶的数据
EXTERN int StackSize(Stack* ps);//获取栈的有效元素个数
EXTERN int StackEmpty(Stack* ps);//检测栈是否为空。空为1,不空为0
EXTERN void StackDestory(Stack* ps);//栈销毁
EXTERN void StackPrint(Stack* ps);//栈数据打印
总结:我们使用extern "C"时,总是在c++文件中使用。只需要记住这一点,其他就好弄了。?
7.内联函数
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率,inline和register一样都是建议。需要看编译器自己,不是你写了inline就一定会展开。
int add(int x, int y)
{
int ret = x + y;
return ret;
}
int main()
{
add(1,2);
add(1, 2);
add(1, 2);
add(1, 2);
add(1, 2);
add(1, 2);
return 0;
}
在调用函数是需要建立函数栈帧,?栈帧中要保存一些寄存器,结束后又要恢复,可以看到在建立函数栈帧都是有消耗的,对于频繁调用的小函数,能否优化一下。
在C语言中使用了宏,但是在C++中使用inline,一般在debug中是看不到inline的作用的,因为在编译器默认情况下debug是不进行代码优化的,在release中才会进行代码优化。为了方便在debug下查看,需要配置VS:
?
?在没有使用inline时,观察汇编:这里使用call 建立了函数栈帧
?使用inline后:没有建立栈帧,直接在函数中展开计算了
总结:
1.内联是用一种以空间换时间的做法,省去调用函数开销。所以代码很长(大于10行)或者有循环/递归的函数不适宜使用作为内联函数。
2.inline对于编译器而言只是一个建议,编译器会自动优化,如果定义位inline的函数体内有循环/递归等,编译器优化时会自动忽略掉内联。
3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址,链接就会找不到。
在.h中声明和.c定义会报错的,不能分开。因为inline在符号表中不会生成函数地址,而是原地展开。链接时就出问题了,声明内联函数的.o文件在连接时,定义处没有函数地址。
可以在声明处加内联
inline int add(int x, int y);
int add(int x, int y)
{
int ret = x + y;
return ret;
}
也可以直接定义:
inline int add(int x, int y)
{
int ret = x + y;
return ret;
}
|