【C++】_1.C++基础

目录

1.C++关键词

2.C++命名空间

3.C++输入和输出

4.缺省参数

4.1 缺省参数的定义

4.2 缺省参数的分类

4.3 缺省参数的应用

5.函数重载 

5.1 函数重载的定义

5.2 函数重载的意义

5.3 名字修饰

6.引用

6.1 引用的定义

 6.2 引用特性

6.3 使用场景

6.4 传值、传引用效率(性能)比较

6.5 指针和引用的区别

7.内联函数

7.1 概念

7.2 特点

8.auto关键字(C++11)

8.1 auto简介

8.2 auto使用场景

 8.3 auto不能使用场景

9.指针空值nullptr


正文:

1.C++关键词

C++G共63个关键字,C语言共32个关键字,此处不做详细介绍。

2.C++命名空间

在写C++代码前,我们总是会默认编写一行代码为using namespace std;

 C++为了防止命名冲突,将标准库的东西都放置于std的命名空间内。

这行代码的含义是:展开C++标准库。

(1):

#include<iostream>
int main()
{
printf("%d",rand);
return 0;
}

编译器会报错:

 因为:当运行时会默认首先在局部域寻找变量rand,寻找失败时会到全局域中寻找,全局域中有两个rand,一个是标准库中的rand函数,一个是定义的全局变量rand=0;此时编译器就会报错。

(2):

#include<iostream>
namespace bit
{
	int rand = 0;
}
int main()
{
	printf("%d\n", rand);
//打印的是标准库中rand函数的地址
	printf("%d\n", bit::rand);
//打印的是bit命名空间中的rand
	return 0;
}

 设置bit命名空间,然后通过指定打印的rand是bit命名空间的rand,成功打印0;

 (3):

namespace bit
{
	int rand = 0;
}
using namespace bit;
//展开bit命名空间
int main()
{
	printf("%d\n", rand);
	return 0;
}

 using namespace bit;相当于将命名空间bit中的内容展开放置于全局域中,即全局域中又出现了2个rand,此时编译器仍会报错。

(4):

C++将标准库的内容放置标准库std中是为了避免命名冲突,而using namespace std;将标注库的东西全部暴露在全局域中,其实并不方便。

在日常练习中我们可以直接展开std命名空间:

#include<iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
}

但是在项目中还需尽量避免using namespace std;的使用,可以通过指定命名空间+展开常用命名空间的方法:

#include<iostream>
#include<vector>
using std::cout;
using std::endl;
//将std中的cout与endl展开放置全局域中
int main()
{
	std::vector<int>v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	cout << "hello" << endl;
	cout << "world" << endl;
	return 0;
}

(5):支持自定义命名空间

namespace 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;
		}
	}
}

 同时,同一个工程中允许存在多个形同名称的命名空间,比如我们在test.h中也定义了namespace N1,编译器会将其合并在同一个命名空间中:

//test.h
//namespace N1
{
int Mul(int left,int right)
{
return left+right;
}
}

3.C++输入和输出

C++改进了C语言在输出时需要指定类型这一特点,可以自动识别类型

using namespace std;
int main()
{
	int i;
	double d;
	//  >>流提取运算符
	cin >> i >> d;
	//  <<流插入运算符
	cout << i << endl;
	//endl表示换行操作,等同于
	//cout<<i<<'\n';
	cout << d << endl;
	return 0;
}

 PS: ①:

 ② : IO流控制精度,输出宽度等等操作都较为麻烦,在编写代码中如果需要,可以进行C语言的穿插,二者兼容,如:

#include<iostream>
using namespace std;
int main()
{
float d=0;
cin>>d;
cout<<d<<endl;
printf("%.2lf",d);
return 0;
}

 ③ :使用cin标准输入 和cout标准输出时,必须包含<iostream>头文件以及std标准命名空间。

早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件分开,也为了正确使用命名空间,规定C++头文件不带.h。部分旧编译器(vc 6.0)还支持<iostream.h>的使用,但大部分更新的编译器已不支持,故而推荐使用<iostream>+std的方式。

4.缺省参数

4.1 缺省参数的定义

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

#include<iostream>
using namespace std;
void Fun(int a=0)
{
	cout << a << endl;
}
int main()
{
	Fun(1);
	Fun(2);
	Fun(3);
	Fun();
	return 0;
}

输出结果为:

4.2 缺省参数的分类

(1)全缺省:多个参数全部缺省

#include<iostream>
using namespace std;
void TestFunc(int a = 10, int b = 20, int c = 30)
{
	cout << "a=" << a << '\t';
	cout << "b=" << b << '\t';
	cout << "c=" << c << '\t' << endl;
}
int main()
{
	TestFunc(1, 2, 3);
	TestFunc(1, 2);
	TestFunc(1);
	TestFunc();
	return 0;
}

运行结果如下: 

(2)半缺省:

#include<iostream>
using namespace std;
void TestFunc(int a , int b = 10, int c = 20)
{
	cout << "a=" << a << '\t';
	cout << "b=" << b << '\t';
	cout << "c=" << c << '\t' << endl;
}
int main()
{
	TestFunc(1, 2, 3);
	TestFunc(1, 2);
	TestFunc(1);
	return 0;
}

运行结果如下:

 PS:①:无论是全缺省还是半缺省,都是从左往右传参

void Fun1(int a=1,int b=2,int c=3)
{
//...
}
int main()
{
Fun1(20,30)
return 0;
}

即在上文代码中,20必须传参给a,30必须传参给b; 

同时,下文代码的格式也是错误的:

void Fun(int a=1,int b=2,int c=3)
{
//...
}
int main()
{
Fun( ,20, );
return 0;
}

②:半缺省必须从右往左连续缺省,不能间隔

即下文的缺省方式都是错误的:

void Func1(int a=1,int b,int c=2);
void Func2(int a,int b=3,int c);

③:缺省参数不能在函数定义和声明中同时出现:

如果声明和定义同时出现,而两个位置的缺省参数不同,那么编译器就无法确认到底使用哪个缺省值。

在声明中确定缺省参数即可,在定义中确定缺省参数无效

4.3 缺省参数的应用

#include<iostream>
using namespace std;
struct Stack
{
	int* a;
	int top;
	int capacity;
};
void StackInit(struct Stack* ps, int capacity = 4)
{
	ps->a = (int*)malloc(sizeof(int) * capacity);
	ps->top = 0;
	ps->capacity = capacity;
}
int main()
{
	struct Stack st1;
	//已知需要插入100个数据的空间,则实参定为100
	StackInit(&st1, 100);
	struct Stack st2;
	//当不确定容量时,不传递容量实参,缺省参数为4,提前开好空间,初始插入数据避免扩容
	StackInit(&st2);
	return 0;
}

5.函数重载 

5.1 函数重载的定义

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

#include<iostream>
using namespace std;
//形参类型不同
int Add(int left, int right)
{
	return left+right;
}
double Add(double left, double right)
{
	return left + right;
}
//形参顺序不同
void func(int i, char ch)
{
	cout << "void func(int i,char ch)" << endl;
}
void func(char ch, int i)
{
	cout << "void func(char ch,int i)" << endl;
}
//形参个数不同
int Mul(int x, int y, int z)
{
	return x * y * z;
}
int Mul(int x, int y)
{
	return x * y;
}
int main()
{
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 2.2) << endl;
	func(1, 'a');
	func('a', 1);
	cout << Mul(2, 3, 4) << endl;
	cout << Mul(2, 3) << endl;
	return 0;
}

即函数重载时,要让编译器根据参数可以区分到底将形参传递给哪个函数即可。

PS:返回值不同不构成重载:

short Add(short x,short y)
{
return x+y;
}
int Add(short x,short y)
{
return x+y;
}

上述代码不构成重载。

5.2 函数重载的意义

C++语法中不需要区别输出数据类型,cout<<可以直接进行类型的识别,其本质就是函数重载:

 函数重载的意义,在于调用函数时非常方便,就像在调用一个函数一样;

5.3 名字修饰

C++支持函数重载而C语言不支持,这与C++的名字修饰有关。

此处不做详细解释。

6.引用

6.1 引用的定义

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

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

 6.2 引用特性

(1)引用在定义时必须初始化;

int a=1;
int& b;

上文代码错误,必须阐明定义的是哪一个变量的引用; 

(2)一个变量可以有多个引用,也可以给引用定义引用:

    int a = 1;
	int& b = a;
	int& c = a;
	int& d = c;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << &d << endl;

 输出结果为:

 虽然命名不同,但是变量abcd均代表同一个地址存储的变量;

(3)引用一旦引用一个实体,再不能引用其他实体;

	int a = 1;
	int& b = a;
	int x = 10;
	b = x;
	cout << &a << endl;
	cout << &b << endl;
	cout << &x << endl;

 输出结果为:

a与b的地址相同,x的地址与ab不同;

b已经是a的引用,则不能再将b变为x的引用,b=x的含义是将x赋值给b而非b是x的引用;

PS:

//1.当&符号处在在类型与变量之间时,表示引用
int a=0;
int& b=a;

//2.当&符号单独与变量存在时,表示取地址
cout<<&b<<endl;

6.3 使用场景

(1)作参数——作为输出型参数

① 应用于实现交换的函数:

#include<iostream>
using namespace std;
void Swap(int& r1,int& r2)
{
	int& tmp = r1;
	r1 = r2;
	r2 = tmp;
}
int main()
{
	int a = 0, b = 2;
	Swap(a, b);
	return 0;
}

② 应用于单链表:

#include<iostream>
using namespace std;
typedef struct SListNode
{
	int data;
	struct SListNode* next;
}SLTNode;
void SListPushBack1(SLTNode** pphead, int x)
{
//...
}
void SListPushBack2(SLTNode*& phead, int x)
{
	if (phead = NULL)
	{
		phead = (SLTNode*)malloc(sizeof(SLTNode));
	}
//...
}
int main()
{
	SLTNode* list = NULL;
	SListPushBack1(&list, 1);
	SListPushBack1(&list, 2);
	SListPushBack1(&list, 3);
	SListPushBack2(list, 1);
	SListPushBack2(list, 2);
	SListPushBack2(list, 3);
	return 0;
}

PS:输入型参数:传入函数供使用的参数;

        输出型参数:函数内改变,在函数外拿到已经改变后的参数;

(2)作返回值

① 传值返回:

② 传引用返回:

using namespace std;
int& Count()
{
	int n = 0;
	n++;
	//...
	return n;
}
int main()
{
	int& ret = Count();
    cout << ret << endl;
	cout << ret << endl;
	return 0;
}

运行结果如下:

传值返回方式返回的是n的临时拷贝,而传值返回方式返回的是n的别名,当Count函数调用完毕,变量n原先所在的空间已经归还,而此时却将n的别名作为返回值给ret,即将原存储变量n的那块空间的值给ret,即非法占用了已归还操作系统的空间。 

当函数调用完毕,n及其引用值所在的Count栈帧内的空间都会被销毁,即n及其引用值地址丢失,故而ret的结果是未定义的,栈帧销毁时,系统清理栈帧会将ret置为随机值。

第一次输出结果仍为1是因为原栈帧还未被随机值覆盖,第二次输出结果为随机值是因为第一次调用的printf函数占用了Count函数的栈帧,将这块区域被随机值覆盖,当main函数再次call printf时就会打印随机值。

故而,其实上述代码使用引用返回本质是错误的,发生了越界行为,结果没有保障。

PS:(1)如果出了函数的作用域,返回对象销毁(如函数中的局部对象),则一定不能用引用返回,一定要用传值返回。

(2)当函数内创建变量用static修饰时,变量出函数作用域并未被销毁,就可以使用引用返回。

即下文代码是正确的:

using namespace std;
int& Count()
{
    static int n = 0;
	n++;
	//...
	return n;
}
int main()
{
	int& ret = Count();
	cout << ret << endl;
	cout << ret << endl;
	return 0;
}

6.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;
}

输出结果如下:

可见,当大对象传参时,传引用效率更高;

PS:① 关于const修饰的权限平移、放大与缩小:

试运行以下程序:

    int a = 10;
	int& b = a;
	const int c = 20;
	int& d = c;

报错如下:

 

 是因为

    //权限平移:
	int a = 10;
	int& b = a;

	//权限缩小:
	int c = 20;
	const int& d = c;
	//权限由可读可写变为仅可读,权限缩小;

	const int e = 30;   //const修饰变量e表示:变量e具有常性,不能修改
	//权限放大:
	//int& f = e;       
	//此处将f定义为e的别名,意味着代表同一块空间,
	//但定义f的类型却是int,将一块不能改变内容的空间变为可改变

权限可以平移和缩小,但不可以放大。

同时注意,权限的缩小和放大只针对于存在引用时的状况,一般变量的传参不需要考虑权限的缩小放大。在引用存在的函数传参时,建议用const修饰形参避免权限扩大出现错误。

② 隐式类型转换:

    int i = 10;

	double j = i;       //隐式类型转换

    //错误写法:
	double& k = i;    
	//类型转换时,会产生一个double类型的临时变量
	//i将值赋给临时变量,再由临时变量赋给k
	//临时变量具有常性,故而不可直接将可读可写的变量i赋值给只可读的临时变量
	//此行为导致权限放大,故而错误;

	const double& k = i;
	//const修饰后,即可保证权限的缩小与平移,编写正确;

同时请注意,强转等类型转换都不会改变原变量的类型,都会产生一个临时变量;

故而建议使用引用时建议使用const,会减少可能出现的错误;

③ 权限的缩小平移放大等只针对指针和引用,对于函数调用等普通变量之间赋值情况与之无关:

运行以下代码:

void func1(int n)
{ 
	cout << n << endl;
}
int main()
{
	int a = 10;
	const int b = 20;
	func1(a);
	func1(b);
	func1(30);
	return 0;
}

输出结果如下:

当函数为引用传参时,建议用const修饰

void func2(int& n)
{
	cout << n << endl;
}
int main()
{
	int a = 10;
	const int b = 20;

	func2(a);
	func2(b);   //错误
	func2(30);  //错误

    return 0;
}

报错如下: 

在传参时,如果形参为引用,那么当const修饰的变量或常量作为实参进行传参时,就会出现权限的放大而导致错误,故而建议当函数形参为引用时,使用const修饰形参,避免出现权限放大的情况:

void func2(const int& n)
{
	cout << n << endl;
}
int main()
{
	int a = 10;
	const int b = 20;

	func2(a);
	func2(b);
	func2(30);
	return 0;
}

上文代码正确; 

6.5 指针和引用的区别

(1)语法角度而言:

① 引用没有开空间,指针开了4或8个字节;

② 引用在定义时必须初始化,指针没有要求;

③ 引用在初始化时引用一个实体后就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体;

④ 没有NULL引用,但有NULL指针;

⑤ 在sizeof中含义不同,引用结果为引用类型的大小,指针始终是地址空间所占字节个数;

⑥ 有多级指针,但没有多级引用;

⑦ 访问实体方式不同,指针需要显式解引用,引用依赖于编译器处理;

⑧ 引用比指针使用起来相对更安全;

(2)从汇编码看:

底层角度而言,引用底层是用指针实现的;

总而言之,指针更强大更复杂更危险,引用更局限更安全更简单。

7.内联函数

7.1 概念

(1)以inline修饰的函数叫内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率;

比如像Swap这种短小的函数需要频繁的、如上万次调用,C语言会利用宏函数,C++则利用inline函数。

(2)对于C的宏函数,宏的缺点有:a.可读性差,b.没有类型安全的检查,c.不方便调试等等。'

优点有:a.代码可维护性增强, b.宏函数提高效率,减少栈帧建立

(3)查看方式:

1.在release模式下,查看编译器是否生成call 函数名;

2.在debug模式下,需要对编译器进行设置,否则不会展开;

(4) 尽量使用const,enum,inline替代#define;

7.2 特点

1.inline是一种以空间换时间的做法,省去调用函数额外开销,故而代码较长或有循环、递归的函数不宜使用作为内联函数;

2.inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数a.内部实现代码指令长度较长(据编译器不同而定)b.体内有循环或递归等,编译器优化时会忽略掉内联;

一般来说,内联机制用于优化规模较小、流程直接、频繁调用的函数,很多编译器都不支持内敛递归函数,一个75行的函数也不大可能在调用点内联展开。

3.inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址则链接找不到;

8.auto关键字(C++11)

8.1 auto简介

auto用于自动推导变量:

8.2 auto使用场景

(1)与范围for结合使用(可用于数组遍历):

using namespace std;
int main()
{
	int a[] = { 1,2,3,4,5,6 };
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	for (int i = 0; i < sizeof(a) / sizeof(int); ++i)
	{
		a[i]++;
		cout << a[i] << " ";
	}
	cout << endl;
	//范围for
	//自动依次取a的数据赋值给e
	for (auto e : a)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : a)
	{
		e++;
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

(2)与指针结合使用:

using namespace std;
int main()
{
	int x = 10;
	auto a = &x;
	//a类型为int* 
	auto* b = &x;
	//b类型为int*,强调一定要传指针
	auto& c = x;
	//c类型为int,强调c是一个引用
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	*a = 20;
	*b = 30;
	c = 40;
	return 0;
}

 8.3 auto不能使用场景

(1)auto不能作为函数的参数

void TestAuto(auto a)
{}

(2)auto不能直接用来声明数组

void TestAuto()
{
int a[]={1,2,3};
auto b[5]={4,5,6};
}

9.指针空值nullptr

 NULL实际上是一个宏,在传统的C头文件中规定:#define NULL 0;

在C++98中,字面常量0既可以是一个整型数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整型常量,如果要将其按照指针方式来使用,必须对其进行强转(void*)0;

PS:

1.在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的;

2.在C++11中,sizeof(nullptr)与sizeof(void*)0所占字节数相等;

3.为了提高代码的健壮性,在后续表示指针空值时建议使用nullptr;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值