C++基础知识(万字总结)

文章目录

目录

文章目录

前言

1.C++的输入和输出

1.1.C++头文件

1.2.C++输入输出

2.缺省参数

2.1缺省参数的定义

2.2缺省参数的分类

**全缺省参数

**半缺省参数

2.3缺省函数的注意事项

3.函数重载

3.1函数重载的定义

**参数个数

**类型

**顺序

**特殊情况**                           

  3.2函数重载的原理                                                                               

4.引用

4.1引用的概念

4.2引用特性

4.3引用的应用

4.3.1指针的引用

4.3.2引用做参数

4.3.3引用做返回值

4.4常引用

4.5引用和指针的区别

5.类联函数

5.1简单的宏

5.2类联函数的概念

 5.3类联函数的特性

6.auto关键字

6.1auto的简介

 6.2auto使用规则

6.2.1. auto与指针和引用结合起来使用

6.2.2. 在同一行定义多个变量

6.2.3auto不能推导的场景

7.. 基于范围的for循环

7.1基本使用

7.2注意事项

8.指针空值nullptr

总结


前言

终于做了这个决定好好地写博客,可能里面还有很多的不足,希望大家在评论区指正,有评论必回访。。


1.C++的输入和输出

1.1.C++头文件

要了解C++的输入和输出,首先要知道C++的头文件。

#include<iostream>
using namespace std;

c++库里的头文件,和c里面的#include<stdio.h>是一样的,输入输出流,那么第二句呢很多参考书上都有这句,这句学了命名空间就大概知道是什么意思,这就是标准命名空间std,写了这句才能直接写cout或者cin。但是这句写了不太好,因为我们,c++库的实现是定义在一个叫std的命名空间中的,这样写了会导致std这个库里的内容全部展开,这样会导致失效。但是平时练习这样写没有关系。

1.2.C++输入输出

cout << "hello world" << endl;
cout << "hello world\n" ;
printf("hello world\n");

cout就是输出,然后<<是流插入,然后那个endl相当于是换行。这个换行还有第二种写法,就是第二行的。

int i = 1;
double d = 1.11;
cout << i << d << endl;

这里我们就发现C++语法中不需要指明变量的类型,直接把变量写上去就行了。

struct Student
{
	char name[20];
	int age;
	// ...
};
int main()
{
   struct Student s = { "特莱维斯", 18 };
	// cpp
	cout <<"姓名:" <<s.name << endl;
	cout << "年龄:" << s.age << endl << endl;
	// c
	printf("姓名:%s\n年龄:%d\n", s.name, s.age);
}

这里可以看出,C++并不一定比C方便,所以具体使用的时候自己考虑一下。

cin >> s.name >> s.age;
cout << "姓名:" << s.name << endl;
cout << "年龄:" << s.age << endl << endl;

scanf("%s%d", s.name, &s.age);
printf("姓名:%s\n年龄:%d\n", s.name, s.age);

cin是输入,>>是流提取运算符,这里的输入也是不用指明变量类型,直接写变量在流提取运算符后面就行了。这里注意一点就不用谢endl了。

2.缺省参数

2.1缺省参数的定义

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

2.2缺省参数的分类

缺省参数分为两类:全缺省参数和半缺省参数

**全缺省参数

void Func(int a = 10,int b = 20, int c = 30)
{
	cout << "a =" << a << endl;
	cout << "b =" << b << endl;
	cout << "c =" << c << endl;
}
int main()
{
    Func();
    Func(1);
	Func(1, 2);
	Func(1, 2, 3);
}

 大家可以看到上述图片的结果,需要说明的一点是调用函数传值得时候是从左往右传的。

不能只传给B,像这样Func(,2,);,这样是不行的。

**半缺省参数

void TestFunc(int a, int b = 10, int c = 20)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl<<endl;
}
int main()
{
    TestFunc(1);
	TestFunc(1,2);
	TestFunc(1,2,3);
}

 这样就是半缺省参数的调用,很多同学就会问那么可不可以这样调用。

TestFunc(1, ,2);

这样是不行的,因为这样是语法错误。我给大家看一下编译器运行的结果。

 那么有的同学就会问,那么定义半缺省函数可不可以这样定义。

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

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

这样也不行,因为缺省函数是必须要从左往右开始缺省,如果像上面一段代码的编译器就会报错。

2.3缺省函数的注意事项

半缺省参数必须从右往左依次来给出,不能间隔着给

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

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

C语言不能实现缺省函数

3.函数重载

3.1函数重载的定义

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

**参数个数

int Add(int left, int right)
{
	cout << left + right << endl;
	return 0;
}
int Add(int left,int mid,int right)
{
	cout << left + mid + right << endl;
	return 0;
}
int main()
{
    Add(1,9);
    Add(1,5,7); 
}

**类型

int Add(int left, int right)
{
	cout << left + right << endl;
	return 0;
}

int Add(double left, double right)
{
	cout << left + right << endl;
	return 0;
}
int main()
{
    Add(1,8);
    Add(1.6,1.9);
}

**顺序

int Add(int left, double right)
{
	cout << left - right << endl;
	return 0;
}


int Add(double right, int left)
{
	cout << left - right << endl;
	return 0;
}
int main()
{
    Add(1,1.9);
    Add(1.9,1);
}

**特殊情况**                           

返回值不同,不能构成重载                                                                                          

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

缺省值不同,不能构成重载

void f(int a)
{
	cout << "f()" << endl;
}

void f(int a = 0)
{
	cout << "f(int a)" << endl;
}

构成重载,但是使用时会有问题 : f();调用存在歧义                                                                     

void f()
{
	cout << "f()" << endl;
}

void f(int a = 0)
{
	cout << "f(int a)" << endl;
}

int main()
{
	// f(); // 调用存在歧义
	 f(1);

	return 0;
}

  3.2函数重载的原理                                                                               

  我们应该知道C语言是不支持函数重载的,但是为什么呢?

这个时候我们应该要知道编译器的原理,分为: 预处理(头文件展开,宏替换,条件编译,去掉注释)-> 编译(检查语法,生成汇编代码)->汇编(汇编代码转为二进制代码)->链接。那么我们现在来看一个程序。

 现在我们来分析C语言为什么不支持函数重载。

首先在func.c是函数定义,test.c是主函数进行函数调用,只有函数定义了才能产生地址。在test.c中,有最上面有#include“func.h”这是函数声明,在编译的时候调用函数会生成一句call_f(?) ,你可能会问为什么会是_f(),因为C语言的语法规定在编译的时候有函数声明的时候就用函数名来找。而后面的(?),是为了在链接的时候找地址用的,如果函数定义也在test.c里面,那也是不行的,test.c里面也会生成符号表,就是因为C语言的命名规则导致他生成的符号表发生歧义,具体情况参照func.c。

接下来到了func.c ,这里也有函数定义就不多说了,然后函数定义里面的内容。

                                              

而这个时候函数定义的里面会生成一个符号表。

 这个时候我们做个总结。

那么现在再来讨论一下C++为什么支持函数重载,是引入了一个函数名命名规则。

首先还是test.c,不一样的是编译的时候不是_f(?),而是_Z1fi(?),为什么是这个呢?因为C语言和C++命名规则不一样,C语言是根据函数名来的,而C++根据_Z+函数名长度+函数名+参数首字母。这样就不会在链接的时候产生歧义,_Z1fi(?)这里面的问号是在链接的时候得到的地址。

 然后就是func.c,生成的符号表也是不同的,这里可以很明显看出两个符号表的区别。

 那么这里再看一下func.c和test.c编译的内容。

那么也来总结一下。

 

现在再来熟悉一下C语言和C++的命名的区别。

4.引用

4.1引用的概念

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

int  main()
{
	int a = 10;
	int &b = a;

	cout << a << endl;
	cout << b << endl;
}

 引用相当于个a取了另外一个名字叫b,他们共用一片空间。

注意:引用的必须和原来的变量是同一个数据类型。

4.2引用特性

1. 引用在定义时必须初始化

int main()
{
	int a = 10;
	int &b;
}

2. 一个变量可以有多个引用

int main()
{
	int a = 10;
	int &b = a;
	int &c = a;

	int &d = b;

}

3. 引用一旦引用一个实体,再不能引用其他实体

int main()
{
	int a = 10;
	int& b = a;

	int c = 20;
    b = c;

}

  这里是给c变别名?还是把c的值给b呢?

 

 这里可以看到是把b所在空间的值变成c,这里就不是给c取别名了。

4.3引用的应用

4.3.1指针的引用

第一个就是以前的二级指针,以前的二级指针可能不是很好理解,这个可以用引用来进行替换,来看一段代码。任何类型都引用,指针也不例外,这里叫指针的引用。

int main()
{
	int a = 10;

	int *p1 = &a;
	int** p2 = &p1;
	int*& p3 = p1;
}

这里相当于是有一块空间叫a里面存的值为10,有一个指针变量叫p1它里面存的是a的地址,然后给这个p1取一个别名叫p2。

 

 这里的如何用引用来替换二级指针。

4.3.2引用做参数

void Swap(int *px, int *py)//传地址调用
{
	int a;
	a = *px;
	*px = *py;
	*py = a;
}

void Swap(int&rx, int& ry)//传引用
{
	int a;
	a = rx;
	rx = ry;
	ry = a;
}

void Swap(int x, int y)//传值
{
	int a;
	a = x;
	x = y;
	y = a;
}
int main()
{
	int x = 10, y = 20;
	cout << x <<" "<< y << endl;
	Swap(&x,&y);
	cout << x << " " << y << endl;
	Swap(x,y);//这里传值和传引用产生了歧义,虽然他们构成重载函数。
	cout << x << " " << y << endl;
}

4.3.3引用做返回值

要学习这个内容,我们首先要了解一个东西,就是传值返回。

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

int main()
{
	int ret = Add(1, 2);
	cout << ret << endl;
	return 0;
}

 我们现在看这个图来分析一下,首先main和Add函数都会生成一片空间,主函数到调用Add函数哪里进入Add函数,在Add函数生成的空间里面会生成三个小的空间存放变量a,b,c,然后做完a+b后,return返回c的值,这时c的值会存放在一个临时变量里,Add函数生成的空间会返还给内存,然后函数调用完了之后,ret的值是临时变量的,不是c的。

我们再来看一下引用返回值。

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

int main()
{
	int& ret = Add(1, 2);
	cout << ret << endl;
}

 这里大部分和上面的一样,我现在就只说不一样的,当add这个函数做完,然后返回的值是是c的引用(别名),然后也要一个引用来接受,这个图上写错了,应该是int &ret的,这个时候很多人就会问,那么add函数结束了不是应该会回收它自己的空间吗,哪c的引用ret不是没用了吗。这个和编译器有很大的关系,有的编译器会在函数返还空间的时候,把里面的数据清零,而有的不会,我用的vs2013就不会,这就会造成问题。下图的问题就是从新调用了add函数后,原来那片空间的数据被覆盖了,而ret指向的空间还是那一片,所以就变成了30。

然后我还想说的就是只要是调用函数就会消耗一片空间,printf和cout也一样,调用了函数后如果有值那片空间就会被覆盖。

那么怎么才能避免这种情况呢?比如用static,出了函数的作用范围都还可以有效果。

需要说明一点的是malloc生成的空间,free会数据清零。

很多就问了学了这个引用到底有什么用,在值传递的时候,感觉也没什么优势啊。当数据特别大的时候进行值传递很浪费时间和空间,因为每次调用函数都要把值进行复制,而传引用就不用了,那么我们现在来看一个例子。

#include <time.h>
struct A{ int a[10000]; };

A a;
// 值返回 -- 每次拷贝40000byte
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;
}

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()
{
	TestReturnByRefOrValue();
	//TestRefAndValue();

	return 0;
}

 这里就可以看到传引用比传值的优势了。

1、引用传参和传返回值,有些场景下面,可以提高性能。(大对象+深拷贝对象-)
2、引用传参和传返回值,输出型参数和输出型返回值。通俗点说,有些场景下面,形参的改变可以改变实参。
 有些场景下面,引用返回,可以改变返回对象。--了解一下

4.4常引用

// 权限放大  不可以
	//const int a = 10;
	//int& b = a;

	// 权限不变 可以
	const int a = 10;
	const int& b = a;

	// 权限的缩小 可以
	int c = 10;
	const int& d = c;

这段代码很容易理解,就不用多说了。

int  main()
{
	double d = 1.11;
	
	int i1 = d;
	//int &i2 = d;
	const int &i3 = d;
	
}

其中int i1=d;这段代码就是c语言的整形提升,相当于就是d的值存在了一个临时变量里,而它具有常性(不可修改),然后就把临时变量里的值又给i1,这时i1的值会变成整形。只有类型转换才能生成临时变量!!!

 int &i2=d,这个代码为什么会不行呢?因为d的值存在了临时变量里,就是临时变量具有常性,它的权限就会变大,所以就不能引用。,所以就会加要句const,使它的权限变小,这个时候i3是临时变量的别名。

4.5引用和指针的区别

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

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

3. 没有NULL引用,但有NULL指针

4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)

5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

6. 有多级指针,但是没有多级引用

7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

8. 引用比指针使用起来相对更安全

5.类联函数

5.1简单的宏

要学习了类联函数我们要知道宏,那么我们来写一段关于宏代码。写这个函数的宏时,很多人可能第一句会写成 #define Add(x,y) x+y,这句进行简单的调用是么有问题的,但调用的时候把他改成10 * Add(1, 9),那么就不行了,我们把它的表达式写出来就变成了 10*1+9 ,这个很明显就看出来了答案是19,和我们预期的结果不同,这时我们的宏就改成 #define Add(x,y) (x+y),这样还是有点问题,如果我们调用函数时传的参数是 (a&b, a | b) ,我们把表达式写出来,就是a& b+a|b,通过c语言的学习,我们知道 + 的优先级比 & 和 | 高,所以就是先进行 + 的运算,所以这样写的宏还是有点问题,这时我们就改成了 #define Add(x,y) ((x)+(y))。 

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

//#define Add(x,y) x+y
//#define Add(x,y) (x+y)
#define Add(x,y) ((x)+(y))

int main()
{
	cout << Add(1, 9) << endl;//cout<<1+9<<endl;
	cout << 10 * Add(1, 9) << endl;//cout<<10*1+9<<endl;

	int a = 0, b = 1;
	cout << Add(a&b, a | b) << endl;//cout<<a& b+a|b<<endl;
}

5.2类联函数的概念

通过写这个Add函数的宏,我们发现这个宏还是比较复杂的,所以为了弥补C语言的不足,C++就有了类联函数。为什么要学习宏和类联函数呢?

因为调用函数的时候会消耗函数栈帧,栈帧中又要保存一些寄存器,结束后又要恢复,我们看到是要消耗的。

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

int main()
{
	int a = 1, b = 2;
	int ret = Add(a, b);
	cout << ret << endl;

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

int main()
{
	int a = 1, b = 2;
	int ret = Add(a, b);
	cout << ret << endl;

}

 在Debug环境下,把上面两段代码转为汇编语言,illine直接就把函数展开,而不用inline的就要调用函数。

还有就是在Debug下面还要改一些东西,这样才能让inline有作用。

 5.3类联函数的特性

1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜 使用作为内联函数。

2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。

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

4.代码短小,且平凡调用的情况可以使用inline。

6.auto关键字

6.1auto的简介

在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但遗憾的是一直没有 人去使用它,大家可思考下为什么? C++11中,标准委员会赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型 指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得

int main()
{
	//const int a = 10;

	//auto b = a;
	auto d = 20;
	auto e = 1.6;
	auto f = 'a';
	auto g = "asda";
	
	//cout << typeid(a).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
	cout << typeid(f).name() << endl;
	cout << typeid(g).name() << endl;
}

 可以看到编译器的结果,auto是可以推导数据的类型。而cout << typeid(b).name() << endl;是测试变量的类型,这里不用深究,了解就行。还有就是要注意auto出的变量必须初始化。

 auto还可以用来结束函数返回值。返回值如果是char和double类型的都会转化为int。

 6.2auto使用规则

6.2.1. 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 = 100;
	*b = 200;
	c = 300;
	cout << *a << endl;
	cout << *b << endl;
	cout << c << endl;
}

 

这里的auto a = &x;和auto* b = &x;其实是一个意思,都是转为指针,c变量是引用。

6.2.2. 在同一行定义多个变量


int main()
{
	auto a = 1, b = 2;
	auto c = 3, d = 4.0;
}

第一行是可以的,而第二行就不行了,因为auto定义同一必须是同一类型,不然编译器就会报错。

6.2.3auto不能推导的场景

1. auto不能作为函数的参数

 

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

 

3. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法

4. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式

7.. 基于范围的for循环

7.1基本使用

在以前的for循环就是用

    
int main()
{
    int array[] = { 1, 2, 3, 4, 5 };
	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
		array[i] *= 2;

	for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); ++p)
		cout << *p << "  ";
	    cout<<endl;

}

现在c++引入了一个新的for循环。


int main()
{
	
	int arr[] = { 1, 2, 3, 4, 5 };
	for (auto&e : arr)
		e *= 2;
	for (auto&e : arr)
		cout << e << "  ";

}

 可以看到运行的结果是一样的。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。

7.2注意事项

void TestFor(int a[])
{
	// 范围必须是数组名
	for (auto& e : a)
		cout << e << endl;
}
int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	TestFor(array);
}

这段代码是有问题的,因为这个for循环必须要一个范围,而这个范围的条件也有点苛刻,必须是函数名,而在函数里不行。

8.指针空值nullptr

我们来看一段代码

void f(int a1)
{
	cout <<"int" << endl;
}


void f(int* a2)
{
	cout << "int*" << endl;
}

int main()
{
	f(0);
	f(NULL);

	f(nullptr);
}

 为什么NULL会调用上面的函数呢?因为NULL是一个宏,而它的宏就NULL当成了0,所以调用函数的时候就会调用到错误的函数,所以说在一些场景下NULL会造成一些错误。而nullptr不会。

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

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

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


总结

历经博主几天的奋战终于把篇博客弄出来了,可能有点简陋,但是我会积极的学习,怎么才能写出优质的博客,感谢大家的支持!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值