IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> 1.C++入门+基础 -> 正文阅读

[C++知识库]1.C++入门+基础

目录

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;
}
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2022-01-16 12:51:59  更:2022-01-16 12:54:00 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 10:52:30-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码