C++入门篇

命名空间

命名空间的目的是对标识符的名称进行本地化以避免命名冲突或名字污染

命名空间的定义

namespace + 命名空间名称 {变量,函数}
命名空间可以嵌套定义、也可以分段定义

定义一个 N1 命名空间,以及全局变量 a :

namespace N1 {  
	int a = 0;
	void fun1() {
		printf("N1:fun1()\n");
	};
	namespace N2 {                 //嵌套定义
		int a = 1;
		void fun1() {
			printf("N1:N2:fun1()\n");
		}
	}
}
int a = 10;
void fun1()
{
	printf("fun1()\n");
}
namespace N1 {           //分段定义
	int b;
	void fun2() {
		printf("N1:fun2()\n");
	}
}

测试部分:

void test()
{
	printf("a: %d\n", a);

	printf("N1:a = %d\n", N1::a);
	printf("N1:N2:a = %d\n", N1::N2::a);       //嵌套调用
	using N1::fun2;
	fun2();
	//using N1::a;
	//printf("N1:a = %d\n", a);   //与下两行中a发生冲突定义
	using N1::N2::a;
	printf("N1:N2:a = %d\n", a);
}

在这里插入图片描述

命名空间的使用

命名空间当中成员的使用:
1、命名空间 + :: +成员
2、using 命名空间::成员
3、展开命名空间 :using namespace 命名空间

在同一个工程文件中,可以存在同名的命名空间 — 编译器会自动合并同名命名空间
在同一个作用域中,不能存在同名的成员(函数、变量) — 发生重定义,但在不同作用域中可以存在同名

C与C++

(1)在函数定义方面

Add(int a,int b)
{
    a+b;
}

int Sub(int a,int b)
{
    a-b;
}

以上两种函数定义方式在C语言编译时不会报错,编译成功
但在C++中会报错,提示函数必须有返回值(void / int / …),且必须有 return

(2)在参数方面

int fun()
{
    printf("fun()\n");
}

int main()
{
	fun();
	fun(10);
	fun(10,20);
	fun(10,20,30);
    return 0;
}

在C语言中,调用无参函数时可以传递多个参数值,不会报错
但在C++中会报错,提示函数无参或调用函数不存在

由此说明:C++编译器的检测更加严格

缺省参数

缺省参数是声明或定义函数时为函数的参数指定一个默认值,在调用该函数时候,若没有指定实参则采取默认值,否则直接使用实参。

缺省参数的分类

全缺省参数:所有参数给定默认值
半缺省参数:部分参数给定默认值(参数默认赋值必须是从右往左连续赋值)

void fun(int a)
{
	cout << a << endl;
}
void fun1(int a=10)
{
	//缺省参数
	cout << a << endl;
}
void fun2(int a = 10, int b = 20, int c = 30)
{
	//全缺省参数
	cout << a << " ";
	cout << b << " ";
	cout << c << " ";
}
void fun3(int a, int b, int c = 20, int d = 40)
{
	//半缺省参数
	cout << a << " ";
	cout << b << " ";
	cout << c << " ";
	cout << d << " ";
}

测试部分:

void test()
{
	fun(10);

	fun1();
	fun1(20);

	fun2();
	cout << endl;
	fun2(50);
	cout << endl;
	fun2(50, 60, 70);
	cout << endl;

	fun3(50, 60, 70, 80);
	cout << endl;
	fun3(40,90);
	cout << endl;
}

在这里插入图片描述

注意
1、半缺省参数必须从右往左依次来赋值,不可以间断式或跳跃式赋值

2、缺省参数不在函数声明和函数定义中同时给出(防止产生二义性)

3、缺省值必须是常量或全局变量

4、C语言不支持缺省参数,C++语言支持

函数重载

函数重载:与函数返回值无关,函数的参数列表必须不同(参数个数、参数类型、参数顺序)

函数重载是函数的一种特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数、参数类型、参数顺序)必须不同,常用来处理实现功能类似数据类型不同等问题。
例如,加法函数的使用:

int Add(int a, int b)
{
	return a + b;
}
char Add(char a, char b)
{
	return a + b;
}
double Add(double a, double b)
{
	return a + b;
}

可以看出上述三个加法 Add 函数的形参列表以及返回值都不同,实现不同类型数据的加法,因此构成 Add 重载。

测试部分:

void test()
{
	int a = 1, b = 2;
	char c = 'a', d = 'b';
	double e = 2.0, f = 3.0;

	cout << Add(a, b) << endl;
	cout << Add(c, d) << endl;
	cout << Add(e, f) << endl;
}

使用不同的形参列表会调用不同的 Add 函数,又反汇编可以看出每次调用的函数是不同函数且可以正常运行:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

隐式类型转换

int Add(int a, int b)
{
	return a + b;
}
char Add(char a, char b)
{
	return a + b;
}
double Add(double a, double b)
{
	return a + b;
}

若调用格式:

Add(1,2.4);      //该调用 int 类型还是 double 类型函数?

隐式类型转换时候,可能有两种形式:Add(int,int) , Add(double,double),因此存在二义性

若调用格式:

float f1=12.23;
float f2=23.34;

Add(f1,f2);               //调用时候会进行隐式类型转换 Add(double,double)

C++支持重载

C语言中不支持函数重载,而C++语言支持函数重载,因为C语言当中底层函数信息中只显示函数名,而C++语言中底层函数信息不仅显示函数名还会显示函数形参类型(VS2019编译器)

1、C 语言中底层函数信息:_Add 只包含函数名
在这里插入图片描述
2、C++ 语言中底层函数信息:不仅包含函数名还包含形参信息
在这里插入图片描述

C++函数名处理规则:
以 ? 开始,Add 为函数名,@ 表示函数名结束,@YA 表示后边紧跟返回值和参数类型,@Z 表示返回值参数结束
H-int,N-double,D-char,X-void,F-short,J-long …

在Linux 下,gcc之后函数名的修饰没有发生变化。g++之后,<_z> 以_z开头,紧跟着一个数字(表示函数名所含字母个数),接下来函数名,之后紧跟返回值参数类型(i-int , d-double , pi-int*) ,例如: <_z2Addiii> ===> int Add(int,int)

extern “C” 修饰函数

在C++文件中,使用 extern “C” 修饰函数表示按照 C 语言格式处理函数,即该函数名处理之后在底层为 : _函数名

extern "C" int Add(int a,int b)
{
	return x+y;
}

静态库

生成静态库:编写一段代码,设置 “项目” --> “属性” --> “常规” 将应用程序.exe 更改为 静态库,即可将当前文件设置为静态库文件

使用静态库:在要调用的文件中添加静态库头文件,以及以下代码声明:

#pragma comment(lib," ")                 // "" 中填写自己创建的静态库所在路径

由于定义的静态库可能被C语言所调用,也可能被C++语言调用,因此在声明静态库时候可以使用 extern “C” { //函数方法 }

//静态库文件的头文件代码
#ifdef __cplusplus          //检测是否为C++文件调用,若是则extern不起作用
extern “C” {
#endif

	int Add(int a,int b);
	int Sub(int a,int b);
	
#ifdef __cplusplus  
}
#endif

引用

引用相当于是变量的“别名”,一个变量可以有多个别名,但是一个别名只能属于一个变量,引用不占用内存空间,它和引用的变量指向的是同一块内存空间,修改引用内容相当于修改变量本身。

变量的引用

测试部分:

1、引用与变量本身指向同一块内存空间:

void test()
{
	int a = 10;
	int& ra = a;         //引用
	cout << &a << " " << &ra << " ";
	cout << a << ra << endl;

	int copy = a;
	cout << &copy << " ";
	cout << copy << endl;

}
int main()
{
	test();
	return 0;
}

在这里插入图片描述
可以由分布调试看出,ra 与 a 指向的是同一块内存空间,说明 ra 是 a 的一个别名,而 copy 是 a 的一份拷贝,开辟了新的内存空间。

2、常引用:

void test()
{
	int a = 10;
	int& ra = a;
	cout << a << ra << endl;

	ra = 20;
	cout << a << ra << endl;
}

在这里插入图片描述在这里插入图片描述
为防止产生上图中原始变量被修改的问题,引出常引用,表明指向的被引用 变量不可以被修改:

void test()
{
	int a = 10;
	const int& ra = a;          //const 常量
	cout << a << ra << endl;
}

修改一个被 const 修改的值是不被允许的,因此会产生错误提示如下:
在这里插入图片描述
因此,不能使用 ra 来修改 a 变量的值,但是 a 变量的值可以进行修改

注意:
引用类型必须与引用实体的类型保持一致;
一个变量可以有多个引用;
一个引用变量一旦引用一个实体之后便不能引用其他实体

double d=12.23;
//int& rb=d;           //编译失败,类型不一致
const int& rd=d;     //加 const 之后编译可以通过,因为进行了隐式类型转换,重新创建了一个整形空间,将 d 整形部分存在临时空间了 ,而临时空间内容不能被修改----临时空间具有常性

d=23.34;

cout<<rd<<endl;      // 12,说明 rd 并不是 d 的引用

函数返回值引用

int Add(int a, int b)
{
	return a += b;
}


int& Add(int& a, int& b)        //引用类型
{
	return a += b;
}

在这里插入图片描述

注意:

引用作为函数返回值时,出了函数作用域,若返回对象还未还给系统,则可以引用返回,若已经归还给系统则必须使用传值返回。

int& Add(int a,int b)
{
	int c=a+b;
	return c;
}

//此时引用返回 C 变量会出错,因为 C 是当前函数的局部变量,调用函数结束之后自动释放空间

因此,我们可以采用全局变量来进行引用返回:

int gob=0;
int& Add(int a,int b)
{
	gob=a+b;
	return gob;
}

//此时返回引用不会出错,因为函数调用结束之后,gob 遍历不会被释放

反汇编

void test()
{
	int a = 10;
	int&ra = a;
	ra = 20;

	int copy = a;
	copy = 20;

}

在这里插入图片描述

返回引用与返回值效率

定义一个结构体变量 globalA ,以及返回值函数与返回引用函数:

struct A {
	int a[1000];
};

A globalA;

A fun1()         //返回值
{
	return globalA;
}
A& fun2()          //返回引用
{
	return globalA;
}

测试代码:

void test()
{
	int n;
	cout << "设置循环次数:" << endl;
	cin >> n;

	size_t begin = clock();
	for (int i = 0; i < n; ++i) {
		fun1();
	}
	size_t end = clock();
	cout << "传值 time: " << end - begin << endl;



	begin = clock();
	for (int i = 0; i < n; ++i) {
		fun2();
	}
	end = clock();
	cout << "传引用 time: " << end - begin << endl;
}

在这里插入图片描述

引用 vs 指针

引用与指针在底层的实现方式是一样的,即引用在底层实际上是按照指针的方式进行实现的,因此引用实际上也是有空间的,内存存储的是其引用实体的地址。

但在用户角度理解上,引用就是变量的别名,没有独立空间,和引用实体公用一块空间。

引用 vs 指针:

(1)引用必须进行初始化,指针不一定非要初始化(注意野指针);

(2)引用在初始化时引用一个实体之后,就不能在引用其他的实体,但指针可以在任何时候指向任何一个同类型的实体;

(3)没有 NULL 引用,但是可以有 NULL 指针;

(4)sizeof: 引用的结果是引用类型的大小,而指针在 32 位平台下是4字节,64 位平台下是 8 字节;

(5)引用自加表示的是引用的实体值+1,指针自加表示的是指针向后偏移一个类型的大小;

(6)有多级指针,但是不存在多级引用;

(7)访问实体不同,指针需要显示解引用,引用是编译器自己进行处理的;

(8)引用比指针用起来要更安全。

注意:

void Swap(int& left,int& right)
{
	int tem=left;
	left=right;                       //*pb在此处解引用时候 right=nullptr 报错
	right=tem;
}

int main()
{
	int a=10;
	int b=20;
	Swap(a,b);
	cout<< a << " " << b << endl;
	
	int* pa=&a;
	int* pb=NULL;
	Swap(*pa,*pb);            //调用 Swap 在对 *pb 解引用时候报错,因为 *pb 是NULL;引用在底层等价于指针
	return 0;
}

inline 内联函数

编译时 C++ 编译器会在调用内联函数的地方展开,没有函数压栈的开销,从而提升程序运行的效率。

正常情况下:

int Add(int a, int b)
{
	int c = a + b;
	return c;   //传值
}

int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	cout << ret << endl;

	return 0;
}

在这里插入图片描述

使用内联函数 inline 时:

在debug调试程序时,需要对vs进行一个设置:

在这里插入图片描述

在这里插入图片描述


inline int Add(int a, int b)
{
	int c = a + b;
	return c;   //传值
}

int main()
{
	int a = 10;
	int b = 20;
	int ret = Add(a, b);
	cout << ret << endl;

	return 0;
}

在这里插入图片描述

虽然 inline 省去了调用函数的开销,但是对于代码很长或是有循环/递归的函数不适合使用。

一般不建设 inline 定义与声明分开,分离会导致连接错误。

宏常量、宏函数

宏常量是在定义时没有类型,在预处理阶段直接发生替换,也不会进行类型的检验;
宏不能够进行调试-----------直接替换的

#define 变量名 值

宏的优化

宏常量:常量定义使用 const

const int a=10; //const 修饰表明 a 的值不能改变

并且 const 定义常量时具有类型定义,因此在定义时就能够进行检验

const 会在编译阶段进行替换。

int main()
{	
	const int a=10;
	
	int* pa=(int*)&a;        //类型强转
	*pa=100;
	
	cout << a << " " << *pa << endl;                //打印结果为 10,100 , 因为 a 是常量会直接进行替换
	return 0;
}

宏函数

宏函数也是在预处理阶段进行替换

宏缺陷:

#define MAXSIZE 100


#define Mul(x,y)  x*y
//调用该宏函数可能会替换出错
Mul(1+2,3+4) ======  1+2*3+4 与用户期待的结果不一致

因此在调用宏函数时一般加括号:
 
 #define Mul(x,y)  (x)*(y) 
  

Q:思考下列输出值为?

#define MAX(x,y) (x)>(y)?(x):(y)

int main()
{
	int a = 20;
	int b = 10;
	int ret = MAX(++a, b);
	cout << ret << endl;

	return 0;
}

运行结果为 22,这是为什么呢?
在这里插入图片描述

其实是发生了替换,a 会自增两次:

MAX(++a,b) =====>(++a)>(b)?(++a):(b)

函数定义使用 inline

auto

typedef char* pstring;

int main()
{
	const char* p1;   //编译成功,const修饰 表示指针指向空间的内容不可修改,但指针指向可以修改
	//const pstring p1;    //编译失败  ,此时 const 修饰 p1,表示 p1 的指向不可修改,因此必须进行初始化
	const pstring p1=NULL;  //编译成功的
	
	const pstring p2;   //编译成功
}

在早期 C 语言当中,auto 修饰的变量表明该变量为局部(临时)变量,在 C++ 语言当中 auto 修饰变量会自动推导变量类型:

在这里插入图片描述
注:
1、auto 定义变量时必须初始化(自动类型推导)

2、auto 定义指针,* 可有可无

3、auto 定义引用时,& 不可少

4、一行定义多个变量时表达式推导的类型必须一致

5、auto 不能作为函数参数,但可以推导函数返回值类型(不能推演 void 类型,但可以 void*)

6、auto 不能直接用来声明数组

在这里插入图片描述

范围 for

遍历数组:

int arr[] = { 2,1,4,5,7,8,9,0,3,6 };
	int size = sizeof(arr) / sizeof(arr[0]);

	for (int i = 0; i < size; ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;

范围 for 的使用

for (auto e : arr) {
		//e是范围中元素的一份拷贝
		cout << e << " ";
	}
	cout << endl;

若采用 &e 则表示引用:

在这里插入图片描述

范围 for 的使用:
要遍历的内容一定要是确定范围的

break 与 continue

(1)continue :跳出本次循环

在这里插入图片描述

(2)break:结束循环

在这里插入图片描述

指针空值 nullptr

C++11 中作为关键字

NULL 在程序中会被定义为字面常量 0,或是无类型指针常量 void* :

void test(int a)
{
	cout << "test(int a)" << endl;
}
void test(int* a)
{
	cout << "test(int* a)" << endl;
}
int main()
{
	test(0);      //常量,显然会调用第一个test
	test(NULL);     //思考:会调用哪一个 test 函数?

	return 0;
}

可以发现,函数参数为 NULL 时,调用的也是第一个 test 函数,因为 NULL 被定义为常量 0

在这里插入图片描述

在这里插入图片描述

(博客内容为原创,欢迎评论~)

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值