C++入门篇(1)

目录

1. C++关键字

2. 命名空间

 2.1 命名空间的定义

2.2 命名空间的访问

2.3 命名空间的使用

3. C++的输入和输出

4. 缺省参数

4.1 缺省参数的概念

4.2 缺省参数的分类

5. 函数重载

5.1 函数重载的概念

 5.2 函数重载的原理

5.3 C语言内部函数名修饰规则

5.4 C++内部函数名修饰规则

6. 引用

6.1 引用的概念

6.2 引用的特征

6.3 引用的使用场景

6.4 传值、传引用效率

6.5 引用和指针的区别


1. C++关键字

             C++合计共有63个关键字,而C语言一共有32个关键字,如下:

2. 命名空间

  • 在C语言中,当函数名,变量名存在冲突的时候,C语言没有很好的办法来解决这一问题,例如以下一段代码:
#define  _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>

int rand = 10;

int main()
{
	printf("%d\n", rand);
	return 0;
}
  • 在stdlib.h 这个头文件中,存在rand()函数,与全局变量的 rand 存在命名冲突,因此在编译过程中会报错:

  • 因为rand()函数本质上是一个地址,编译器希望以%p的形式打印,而不是%d
  • 然而当以%p的形式输出的时候又会报重定义的错误:

  • 为了解决这个问题,C++中采用了 命名空间这个方法。

 2.1 命名空间的定义

        定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}可,{}中即为命名空间的成员,例如:

namespace D1
{
	int rand = 10;
}

        命名空间中可以定义变量,函数,类型,例如:

namespace D1
{
	// 命名空间中可以定义变量/函数/类型
	int rand = 10;
	int Add(int left, int right)
	{
		return left + right;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};
	
}

2.2 命名空间的访问

  •         那么命名空间应该如何访问呢?这时需要引用一个操作符  :: 域作用限定符。
  •         在C语言中当全局变量和局部变量存在冲突的时候,编译器的寻找顺序是先在局部寻找,如果找不到就会在全局中寻找,例如:

  • 那如何访问全局中的变量呢?这时候就要用到 域作用限定符:

当与作用限定符前没有命名空间的名字的时候,默认访问全局。 

        类似的,如果 域作用限定符前有命名空间,那么就会访问此命名空间中的内容,例如:

2.3 命名空间的使用

  •  实际上,命名空间一共有三种使用方法,
  •  第一种方法刚才已经提到,就是 命名空间名 :: 参数 ;
  •  第二种就是 使用using namespace 命名空间名称引入,例如:
using namespace D1;


int main()
{
	int x = 6;
	//struct D1::Node phead;
	std::cout << D1::Add(10, 20) << std::endl;
	//printf("%d\n", D1::x);
	return 0;
}
  • 第三种就是使用using将命名空间中某个成员引入,例如:
//test.h
namespace D1
{
	int a = 10;
	int Add(int left, int right)
	{
		return left + right;
	}

	struct Node
	{
		struct Node* next;
		int val;
	};

}
// test.c
using D1::a;
int main()
{
	std::cout << a << std::endl;
	return 0;
}

3. C++的输入和输出

#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
	cout << "Hello world!!!" << endl;
	return 0;
}
#include<iostream>
// std是C++标准库的命名空间名,C++将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
	const char* str = "Hello World!!!";

	const char* str1 = "你好世界!";

	int i = 10 >> 1;
	int h = 0;
	cin >> i >> h;
	cout << i << " " << h << endl;
	cout << str << " " << str1 << endl;
	return 0;
}

  • 使用cout标准输出对象(控制台)和cin标准输入对象(键盘)时,必须包含< iostream >头文件以及按命名空间使用方法使用std。
  • 2. cout和cin是全局的流对象,endl是特殊的C++符号,表示换行输出,他们都包含在包含<iostream>头文件中。
  • 3. << 是流插入运算符,>> 是流提取运算符。

4. 缺省参数

4.1 缺省参数的概念

        缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
 

int Add(int x = 0, int y = 100)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 10;
	int ret1 = Add(a, b);
	int ret2 = Add(a);
	cout << "ret1 = " << ret1 << endl << "ret2 = " << ret2 << endl;
	return 0;
}

4.2 缺省参数的分类

  • 全缺省参数
int Add(int x = 0, int y = 100)
{
	return x + y;
}

int main()
{
	int a = 10;
	int b = 10;
	int ret1 = Add(a, b);
	int ret2 = Add(a);
	int ret3 = Add();
	cout << "ret1 = " << ret1 << endl << "ret2 = " << ret2 << endl;
	return 0;
}

  •  半缺省参数
void Func(int a, int b = 10, int c = 20)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	Func(10);
	cout << endl;
	Func(10, 20);
	cout << endl;
	Func(20, 40, 60);
	return 0;
}

        当我们在写一些程序或者项目的时候喜欢把函数声明和定义分开写,那么能否在函数声明和定义的时候同时缺省参数呢?答案是不行的。

        编译器在编译函数的时候,会经历四个过程,分别是 预处理,编译,汇编,链接

  • 预处理:预处理过程会将 .c /.cpp 文件生成 .i 文件,此时会处理程序中的 宏,注释               以及头文件的展开 等预处理信息。               
  • 编译: 编译过程会将 .i 文件生成 .s 文件,并且会将所有的语言翻译成汇编语言,翻译                的同时便会检查语法是否正确,正常会有以下阶段:词法分析、语法分析、语义              分析、符号汇总。这里的 符号汇总 就会整理文件中所有出现的函数。(此时只              会检查函数的声明是否正确。
  • 汇编:汇编过程会将 .s 文件 生成 .o 文件,同时会将汇编语言转化成机器能识别的二进             制代码,生成目标文件,并将符号整理到表格中,生成符号表
  • 链接:链接的过程会生成.out文件,在gcc的环境下,目标文件通常是按照 elf 的格式存            放的,此时会把目标文件合并,即合并段表,其次还会把所有的 符号汇总。

        缺省参数不能在函数声明和定义的时候同时给,一般会在函数声明的时候给,因为在编译的过程中,编译器会检查语法,如果存在这个函数的声明,则编译的时候会通过,但是不会找到函数的地址,函数的地址是什么呢?函数在编译的过程中会被会变成一条条指令,那函数的地址就是这些指令的第一条指令的地址。只有在链接的过程中,会将所有的目标文件和所有的符号汇总,通过符号表找到对应的函数的地址。

比如以下代码:


void Func(int a, int b = 10, int c = 20)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	Func(10);
	cout << endl;
	Func(10, 20);
	cout << endl;
	Func(20, 40, 60);
	return 0;
}

一定要注意:

  • 半缺省参数必须从右往左依次来给出,不能间隔着给;
  • 缺省参数不能在函数声明和定义中同时出现。


5. 函数重载

5.1 函数重载的概念

我们知道在C语言中不允许同名参数的出现,在C++中,出现了重载的概念: 

  • 函数重载:函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类                    似的同名函数,这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不                      同,常用来处理实现功能类似数据类型不同的问题。
  •  函数参数类型不同
// 1、参数类型不同
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

  •  函数参数个数不同
// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}
  •  函数参数类型顺序不同
// 3、参数类型顺序不同
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

 5.2 函数重载的原理

        我刚才提到过,每个源文件都会通过编译器生成目标文件,例如现在有两个.cpp文件,一个是a.cpp另一个是b.cpp,当我们要从a.cpp文件中调用b.cpp文件中的 某个函数时,在编译之后链接之前a.o中都不会有这个函数的地址,这个函数的地址存放在b.o中,那么怎么办呢?

        这个时候会通过链接的过程,才能找到这个函数的地址,链接器看到a.o要调用这个函数,但是在本文件中并没有这个函数的定义,就会到b.o中找到这个函数的地址,然后连接到一起。

        那么链接的时候,编译器会通过什么名字去寻找呢?如果函数的名字都一样,那么在底层是如何找到我们需要调用的函数的呢?

5.3 C语言内部函数名修饰规则

 我们以Linux下的gcc为例:

  • 这是我在gcc的环境下编写的一段C代码用一下命令进行编译:
gcc -o testc test.c
  • 生成以下文件:

  • 再输入以下命令查看汇编代码:
objdump -S testc
  • 就可以查看函数的命名方式:

5.4 C++内部函数名修饰规则

  •  类似的,我又写了一段C++代码:

  • 再使用以下命令进行编译:
g++ -o testcpp test.cpp
  • 生成以下文件:

  • 再使用以下命令查看汇编代码:
objdump -S testcpp
  • 查看C++函数命名方式:

所以很容易看出来:

  • 在Linux环境下,gcc编名和程序员本身取得名字一样;
  • 在Linux环境下,g++编名函数名修饰方式发生变化,会以_Z开头,函数名的字节数放在后面,之后就是函数的返回类型的字母的缩写。 

当然,在Windows环境下,C++函数名字修饰规则更加复杂,但道理都是类似的


6. 引用

6.1 引用的概念

        引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。

  • 类型& 引用变量名(对象名) = 引用实体,例如:
void TestRef()
{
	int a = 10;
	int& ra = a;//<====定义引用类型
	printf("%p\n", &a);
	printf("%p\n", &ra);
}

6.2 引用的特征

  1. 引用在使用的时候必须初始化
  2. 一个变量可以有多个引用
  3. 引用一旦引用一个实体,之后就不可以引用其他实体

6.3 引用的使用场景

  • 引用做参数 

示例1:

void Swap(int& x, int& y)
{
	int tmp = x;
	x = y;
	y = tmp;
}

int main()
{
	int a = 10, b = 20;
	cout << "a = " << a << " " << "b = " << b << endl;
	Swap(a, b);
	cout << "a = " << a << " " << "b = " << b << endl;
}

示例2:        

        例如我们在写链表的时候一般会使用二级指针传参来改变plist,但是有了C++的引用,就可以把引用传过去:

typedef struct Node
{
	struct Node* next;
	struct Node* prev;
	int val;
}LNode, *PNode;

void PushBack(PNode& phead, int x)
{
	
}

int main()
{
	PNode plist = NULL;

	return 0;
}
  • 引用做返回值

示例1:

        如果函数有返回值,那在返回的过程中不会直接返回那个变量,而是把那个变量临时拷贝到另一个变量中,并且这个变量通常是不可变类型的,例如: 

int Add(int x = 0, int y = 0)
{
	int ret = x + y;
	//因为函数出了栈帧就会销毁,所以这个值通常会被存在另一个临时变量中
	return ret; 
}

        但如果以引用的形式返回,就会大大提高效率,因为引用只是别名,语法上不会开辟另一个空间来存放返回值 。

int& Add(int x = 0, int y = 0)
{
	static int ret = x + y;
	return ret;
}

        注意这里定义的是一个static类型的变量,如果定义的类型只是普通的int类型,因为出了函数栈帧就会被销毁那么这块int 的空间就会被重新分配给操作系统,就变成了野引用,而static类型不是存放在栈中,因此出了函数也不会被销毁。

6.4 传值、传引用效率

        以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

#include <time.h>
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;
}

6.5 引用和指针的区别

  • 在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间

int main()
{
	int a = 10;
	int* pa = &a;
	int& ra = a;
	ra = 20;
	*pa = 30;
	return 0;
}
  •  在底层实现上实际是有空间的,因为引用是按照指针方式来实现的

        C++的引用,对指针使用比较复杂的场景进行一些替换,让代码简单易懂,但不能完全替代指针,原因是引用定义后,不能改变指向。

区别如下:

  • 22
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 10
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值