C++学习笔记——命名空间&缺省参数&函数重载&引用

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kevin980123 https://blog.csdn.net/kevin980123/article/details/83932426

C++学习笔记——命名空间&缺省参数&函数重载&引用

戳这里:我的印象笔记原链接

C++:

1.解决C语言中设计不好或者使用不是很方便的语法—>优化
2.增加新的语法特性
注:extern “C”:在C++工程中,将代码按照C语言的风格来编译

C++关键字 (C++98)----63

此图源@比特科技
在这里插入图片描述

命名空间:

用于解决名字冲突,相当于一个作用域

namespace N1
{
1.变量
2.函数
3.命名空间(嵌套)
}

命名空间的定义方式
1.普通命名空间(只包含变量和函数)
2.命名空间可以嵌套
3.可以创建多个相同名字的命名空间–>合并

命名空间的访问方式
1.在成员前+ N:: (N为命名空间的名字,::为作用域限定符—默认访问全局的)
2.使用using来指定访问变量:using N2::N3::a;
3.使用using来指定访问命名空间:using namespace N2;

标准输入/输出

使用标准输入cin和标准输出cout时,必须包含**头文件以及std标准命名空间**
//为了和c区分,c++98之后头文件不需要包含.h
// using namespace std;标准命名空间

cin标准输入(键盘):int a = 0;
double b = 12.34;
cin>>a>>b;
cout标准输出(控制台):cout<<10<<" "<<12.34<<endl; //好处是不需要加数据格式控制(%d,%c之类)
dec:十进制
oct:八进制
hex:十六进制
二进制可以使用bitset<> 把要输出的数变成二进制存储输出)

“备胎”–>缺省参数

概念:声明或定义函数时为函数的参数指定一个默认值(调用时如果没有指定实参就会使用默认值)

全缺省参数:所有参数都有默认值
对于全缺省参数,如果调用函数时只传递了一部分实参,则实参从左往右传递,其余采用默认值
半缺省参数:部分参数带有缺省值,必须从右向左依次给出
对于半缺省参数,要注意对没有给出缺省值的参数传递实参,实参同样从左往右传递

注意:
1.半缺省参数必须从右往左依次给出,不能间隔着给
2.缺省参数不能在函数声明和定义中同时出现(为了避免出现声明和定义不一致情况),最好在声明的位置
3.缺省值必须是常量或者全局变量
4.C语言不支持(编译器不支持)

“一词多义”–>函数重载:

概念:是函数的一种特视情况,C++允许在同一作用域中声明几个功能类似的同名函数,但这些同名函数的形参列表(参数个数、类型、顺序)必须不同,常用来处理功能类似数据类型不同的问题 //与返回值类型无关,如果只是返回值类型不同,则不能构成重载

二义性无参函数同名的全缺省函数不能同时存在

C语言中不支持函数重载是因为:
C语言中编译器对函数名字的修饰规则:只是简单地在函数名字前添加下划线
C++中支持函数重载是因为:
//在vs中通过只给声明不给定义的方式调用函数,编译成功,链接时报错就可以看到编译器对函数名字的修饰规则↓↓↓
C++中编译器对函数名字的修饰规则(_cdecl:C语言缺省调用约定):
int ADD(int left,int right); —> ?ADD@@YAHHH@Z ?函数名@@YA参数表@Z
参数表(返回值和形参类型)符号表示:
void - X
int - H
unsigned int - I
float - M
double - N
bool - N
char - D
short - F
long - J
unsigned long - K

“外号”–>引用

概念给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

类型& 引用变量名(对象名)= 引用实体

引用特性:
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用了一个实体,就不能再引用其他实体

注意
1.引用类型必须与引用实体同类型
2.引用常量实体时要加const修饰
3.一般情况下,因为引用与实体共用同一块内存空间,所以改变引用的值也就是改变了实体的值
4.引用类型与引用实体不同时加const可以通过编译,此时编译器会为引用创建一个临时变量,这个临时变量具有常属性

使用场景
1.作形参
如果不需要通过形参修改实参的值,最好的方法是在形参引用前加上const修饰
2.作返回值
如果用引用作为函数的返回值类型不能返回函数栈上的空间
在这里插入图片描述
如果一定要用引用作为返回值,返回的变量生命周期一定要比函数的生命周期长
比如可以这样稍作修改:
在这里插入图片描述

传值、传地址、传引用效率比较

#include <Windows.h>
struct A
{
    int array[10000];
};
void TestFunc(A& a)
{}
void TestRefPtr()
{
    A a;
    size_t start = GetTickCount();
    for (size_t i = 0; i < 1000000; i++)
        TestFunc(a);
    size_t end = GetTickCount();
    cout << end - start << endl;
}
int main()
{
    TestRefPtr();
    system("pause");
    return 0;
}

通过上面代码的比较,我们发现引用和指针在传参上的效率几乎相同

引用与指针的区别
在这里插入图片描述
不同点
1.引用在定义时必须初始化,指针没有要求(但最好有一个合法的指向)
2.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任意一个同类型实体
3.没有NULL引用,但有NULL指针
4.在sizeof中含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占的字节个数(32位平台为4个字节)
5.引用自加是引用的实体加1指针自加是指针向后偏移一个类型的大小
6.有多级指针,但没有多级引用(拓展:C++11中将const int&& rra = 10;这种形式称为右值引用,将普通引用称为左值引用
7.访问实体的方式不同,指针需要显示解引用,而引用由编译器自己处理
总结来说也可以得出;
1.引用更安全。因为指针在使用之前必须要判空,而引用不需要(因为规定了引用在定义时必须初始化)
2.引用更简洁。引用在使用时代码比较清晰,也不需要解引用操作,写起来简单,看起来舒服,还可以达到指针的效果

宏的优缺点?

优点:
1.增强代码的复用性。
2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)
2.导致代码可读性差,可维护性差,容易误用。
3.没有类型安全的检查 。
C++有哪些技术替代宏?

  1. 常量定义 换用const
  2. 函数定义 换用内联函数

内联函数

概念:以inline修饰的函数。编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,提升了程序运行的效率
(ps1:内联函数与宏的替换时机不同,宏替换是在预处理阶段,因此不会对参数类型进行检测)
(ps2:查看方式:通过测试发现,在vs编译器Debug模式下为了便于调试,并没有将inline修饰的函数当做内联函数进行展开,1.可以在Release模式下,查看编译器生成的汇编代码中是否存在call Add,在Release模式下,会对代码进行很大的优化,甚至一些没有实际意义的代码都会直接删除,所以它占用空间会很小,但有可能会打乱程序的执行次序。2.也可以在Debug模式下对编译器进行设置,以下给出vs2010设置方式)
在这里插入图片描述

特性

  1. inline是一种以空间换时间的做法,省去调用函数开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
  2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等等,编译器优化时会忽略掉内联

auto关键字(C++11

C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器(“占位符”),auto声明的变量必须由编译器在编译时期推导而得。

int TestAuto()
{
return 10;
}
int main()
{
int a = 10;
auto b = a;
auto c = 'a';
auto d = TestAuto();
cout << typeid(b).name() << endl; // int
cout << typeid(c).name() << endl; // char
cout << typeid(d).name() << endl; // int
//auto e; 无法通过编译,使用auto定义变量时必须对其进行初始化
return 0;
}

auto的使用细则

  1. auto与指针和引用结合起来使用
    用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int x = 10;
auto a = &x;
auto* b = &x;
auto& c = x;
cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
*a = 20;
*b = 30;
c = 40;
return 0;
}
  1. 在同一行定义多个变量
    当在同一行声明多个变量时,这些变量**必须是相同的类型,**否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量。

auto不能推导的场景

  1. auto不能作为函数的参数
  2. auto不能直接用来声明数组
  3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  4. auto在实际中最常见的优势用法就是跟C++11提供的新式for循环,还有lambda表达式等进行配合使用。

//5. auto不能定义类的非静态成员变量
//6. 实例化模板时不能使用auto作为模板参数

auto的优势
1.在拥有初始化表达式复杂类型变量声明时的简化
2.可以免除程序员在一些类型声明时的麻烦,或者避免一些在类型声明时的错误(程序员不用自己去抉择,编译器根据运算的结果推导)

基于范围的for循环

范围for的语法
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环:for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

void TestFor()
{
int array[] = { 1, 2, 3, 4, 5 };
for(auto& e : array)
e *= 2;
for(auto e : array)
cout << e << " ";
return 0;
}

(ps:与普通循环类似,可以用continue来结束本次循环,也可以用break来跳出整个循环。)

范围for的使用条件
1. for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。
2. 迭代的对象要实现++和==的操作。

指针空值nullptr(C++11)

1.C++98中的指针空值
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的
错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
// ……
}

NULL实际是一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif

可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void)的常量*。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦
在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行*强转(void )0

2.nullptr 与 nullptr_t
为了考虑兼容性,C++11并没有消除常量0的二义性,为了避免混淆,C++11给出了全新的nullptr表示空值指针。即:nullptr代表一个指针空值常量。nullptr是有类型的,其类型为nullptr_t,仅仅可以被隐式转化为指针类型,nullptr_t被定义在头文件中:

typedef decltype(nullptr) nullptr_t;

注意:

  1. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
  2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
  3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

【总结】
此图源@比特科技
在这里插入图片描述

最后附上我的学习代码,仅供参考

#include <iostream>
using namespace std;

#if 0
//命名空间

#include <stdio.h>
#include <stdlib.h>



//普通命名空间
namespace N1
{
	int a = 10;
	int b = 20;

	int Add(int left,int right)
	{
		return left + right;
	}
}

//命名空间可以嵌套
namespace N2
{
	int c = 30;
	int d = 40;

	int Sub(int left, int right)
	{
		return left - right;
	}

	namespace N3
	{
		int a = 50;
		int b = 60;

		int Mul(int left, int right)
		{
			return left * right;
		}
	}
}

//可以创建多个相同名字的命名空间
namespace N1
{
	int Div(int left, int right)
	{
		return left / right;
	}
}

//using N2::N3::a;
using namespace N2;
int main()
{
	/*printf("%d\n", ::a);
	printf("%d\n", N1::a);
	printf("%d\n", N2::N3::a);*/
	//printf("%d\n", a);
	printf("%d\n", Sub(d, c));
	system("pause");
	return 0;
}
#endif

#if 0
//标准输入/输出

int main()
{
	int a = 0;
	double b = 12.34;
	cin >> a >> b;
	cout << a <<"	"<< b << endl;
	//cout << hex << a<<endl;
	cout << 10 << "    " << 12.34 << endl;
	cout << "hello world!" << endl;
	system("pause");
	return 0;
}
#endif

#if 0
//缺省参数

int g_a = 9;
void TestFunc(int a = g_a)
{
	cout << a << endl;
}

//全缺省参数:所有参数都有默认值
void TestFunc1(int a = 0, int b = 1,int c=2)
{
	cout << a << " " << b << " " << c << endl;
}

//半缺省参数:部分参数带有缺省值,必须从右向左依次给出
void TestFunc2(int a, int b = 1, int c = 0)
{
	cout << a << " " << b << " " << c << endl;
}

int main()
{
	/*TestFunc();
	TestFunc(10);*/

	/*TestFunc1();
	TestFunc1(10, 20, 30);
	TestFunc1(10);
	TestFunc1(10, 20);*/

	TestFunc2(10);
	TestFunc2(10,20);
	TestFunc2(10,20,30);
	system("pause");
	return 0;
}
#endif

#if 0
//函数重载

int ADD(int left, int right)
{
	return left + right;
}

double ADD(double left, double right)
{
	return left + right;
}

char ADD(char left, char right)
{
	return left + right;
}

//形参列表不同(个数、类型、顺序)
void Test()
{}

void Test(int a)
{}

void Test(double a)
{}

void Test(int a, double b)
{}

void Test(double a, int b)
{}

int main()
{
	//cout << ADD(1, 2) << endl;
	//cout << ADD(1.1, 2.2) << endl;
	//cout << ADD('1', '2') << endl;//ASCLL码相加


	system("pause");
	return 0;
}
#endif

#if 0
//引用

void Swap(int& left,int& right)
{
	int tmp = left;
	left = right;
	right = tmp;
}

//如果不需要通过形参修改实参的值,最好的方法是在形参引用前加上const修饰
int TestFunc(const int& a)
{
	return a;
}
//如果用引用作为函数的返回值类型,不能返回函数栈上的空间
//如果一定要用引用作为返回值,返回的变量生命周期一定要比函数的生命周期长
int& Test()
{
	int x = 1;
	return x;
}

int main()
{
	int a = 10;
	int b = 20;
	const int c = 30;
	int& ra = a;//引用在定义时必须初始化
	int& rra = a;//一个变量可以有多个引用
	//int& ra = b; //引用一旦引用了一个实体,就不能再引用其他实体

	cout << &a << endl;//共用同一块内存空间,所以地址都相同
	cout << &ra << endl;
	cout << &rra << endl;

	const int& rc = c;//引用常量实体必须加const修饰
	const int& rd = 10;

	double e = 12.34;
	const int& re = e;//引用类型与引用实体不同时加const可以通过编译,此时编译器会为引用创建一个临时变量,这个临时变量具有常属性
	e = 100;

	ra = 20;//一般情况下,因为引用与实体共用同一块内存空间,所以改变引用的值也就是改变了实体的值
	rra = 30;

	a = 10;
	b = 20;
	Swap(a, b);

	cout << TestFunc(a) << endl;

	int& rx = Test();
	cout << rx << endl;//10
	cout << rx << endl;//随机值 因为第一次输出时Test函数中x所指向的栈上空间已经被cout压栈覆盖了
	system("pause");
	return 0;
}
#endif

//传值、传地址、传引用效率比较:

#include <Windows.h>
struct A
{
	int array[10000];
};

void TestFunc(A& a)
{}

void TestRefPtr()
{
	A a;
	size_t start = GetTickCount();
	for (size_t i = 0; i < 1000000; i++)
		TestFunc(a);

	size_t end = GetTickCount();
	cout << end - start << endl;
}

int main()
{
	int a = 10;
	int* pa = &a;
	*pa = 20;

	int &ra = a;
	ra = 20;

	TestRefPtr();

	ra++;
	pa++;

	char c = 'a';
	char& rc = c;

	char* pc = &c;
	cout << sizeof(rc) << endl;
	cout << sizeof(pc) << endl;

	system("pause");
	return 0;
}

猜你喜欢

转载自blog.csdn.net/kevin980123/article/details/83932426