【C++笔记】C++基础入门

人生中第一个C++程序

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

1.C++关键字

根据(C++98)标准,C++总计63个关键字,C语言32个关键字。

Mirosoft visual stdio 2022标识:

2.命名空间

在C/C++中,变量,函数和类都是大量存在的,变量,函数,类的名称都将存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的:对表示的名称进行本地化。以避免命名冲突或名字污染

关键字:namespace

关于命名冲突举例:

了解C语言都清楚malloc用于向计算机申请空间的函数。

#include<stdio.h>
//#include<stdlib.h>
int malloc = 0;
int main()
{
	printf("%d\n", malloc);
	return 0;
}

#include<stdio.h>
#include<stdlib.h>
int malloc = 0;
int main()
{
	printf("%d\n", malloc);
	return 0;
}

 

 当包含malloc所在的头文件时,原先的代码直接报错,如果是因为变量命名冲突导致报错,那就非常的搞人心态了。

C++贴心的使用命名空间搞掉了这个问题。

2.1命名空间定义

定义命名空间,需要使用关键字namespace,后面跟命名空间的名字,然后跟上一对{},{}中即为命名空间的成员。

举个栗子:

#include<iostream>
//普通的命名空间
namespace T1//以T1作为命名空间的名字
{
	//内容中可以定义 变量,函数,结构体
	char a;
	void test()
	{
		printf("hello world\n");
	}
	struct ListNode
	{
		int val;
		struct ListNode* next;
	};
}
//进阶
//命名空间就像是一个“大函数”,函数能够嵌套,那么命名空间也能嵌套
namespace T2
{
	char q;
	namespace T3
	{
		int val;
	}
}
//同一个工程中可存在多个名称相同的命名空间,编译器在链接时会将其自动合并
namespace T1
{
	int num;
	double e;
}

 

在namespace中定义的变量,函数,结构体等内容的作用域都将受限在干该命名空间中。

注意:在同一个域中,不能有同名变量。

2.2命名空间的使用

创建了命名空间,解决了可能存在的命名冲突问题,那么该怎么使用这些定义的变量。上文提及过,定义的所用内容的作用域都将受限于其命名空间中,所以直接在其他函数内是无法被使用的。

C++提供了操作符:作用域限定操作符::

1.加命名空间名称及作用域操作符

这就是老实人手动访问

2.使用using将命名空间中成员引入

 

这里是只把T1中的test函数放出来。

3.使用using namespace 命名空间名称引入

这里是将T1中定义的东西全部释放出来。

所以可以解决之前的疑问:为啥要在文件中加using namespace std;

如果不加呢?

可知,cout 和 endl 是包含在C++标准库中的,而且这个库还是一个单独

的命名空间。

在某些情况下,将库完全放出来会出现命名污染的情况,需要注意。

不将标准库中的所有放出来,可以考虑使 1 用和 2.

 

 

 

3.C++输入和输出

针对人生第一个C++程序,hello world的说明:

  • 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含<iostream>头文件以及std标准命名空间。
  • 使用C++输入输出更方便,不需要加数据格式化控制。

4.缺省参数

有个备胎,走在路上都安心了好多(doge)

C++中的函数参数也是可以有备胎的。

4.1缺省函数的概念

缺省函数是生命或者定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则

采用默认值,否则使用指定的实参。

举例子:


void test(int i = 1)
{
	cout << i << endl;
}

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

 

 可见:没有传参时,使用参数为默认值,传参时,使用指定的实参。

4.2缺省参数分类

  • 全缺省参数
    void fun(int a = 10, int b = 20, int c = 30)
    {
    	cout << "a = " << a << endl;
    	cout << "b = " << b << endl;
    	cout << "c = " << c << endl;
    }
    int main()
    {
    	fun();
    	return 0;
    }
    

  • 半缺省参数

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

     

    可知:这是一种部位,且这种部位是顺序的,即无法规定传递的实参2由c来接受。

    注意:

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

    2.缺省参数不能再函数声明和定义中同时出现。

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

    4.C语言不支持

5.函数重载

5.1概念

函数重载:函数的一个特殊情况,C++允许在同一作用域中声明几个功能类似的同名函数,

这些同名函数的形参列表(参数个数/类型/顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

举个栗子:

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

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

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

 

这里可以看到:虽然调用了“相同的函数接口”,但是实际上调用的函数接口是不同的。

构成函数重载的关键:

参数个数,类型,顺序中的至少一个即可。

6.引用

6.1引用概念

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

它和它引用的变量共用一块内存空间

比如:《西游记》里的猴子,敬称“齐天大圣”,法号“悟空”。指的都是同一人。

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

举个例子:

int main()
{
	int a = 10;
	int& b = a;
	cout << "a = " << a<< endl;
	cout << "b = " << b << endl;
	cout << "a: " << &a << endl;
	cout << "b: " << &b << endl;
	b = 20;
	cout << "a = " << a << endl;
	return 0;
}

 

由此可以判定:引用不是一种赋值操作,而是一种绑定操作。

6.2引用特性

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

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

3.引用一旦引用一个实体,就不能再引用其它实体。

int main()
{
    int a = 10;
    //int& b;//会报错
    int& c = a;
    int& d = c;
    printf("%p\n%p\n%p\n",&a,&c,&d);
    return 0;
}

 

6.3常引用

引用针对变量有一套语法,那对于常量语法还能使用吗?

答案是不能。

int main()
{
	int a = 10;
	int& b = a;
	//那要直接给常量10取别名,而不是对变量a取别名。
	//int& c = 10;//报错
	//为什么?
	//常量10只能读不能写,那直接引用会将其权限扩大,是不被允许的。
	const int& c = 10;
	const int& d = a;
	return 0;
}

加上const 可以解决问题,那对于变量引用能否使用const修饰?

答案是可以的。

可以得知:

取别名原则:对原引用变量,权限(读写权限)只能缩小,不能放大。

再看一组代码:

int main()
{
	double a = 1.6;
	//int& b = a; //类型不一样,会报错。
	const int& b = a;//不会报错
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;

}

 

 

但是其中的机制又是什么?

a为浮点型,b为整形,之间的转换涉及类型转换,会生成一个临时变量存放在寄存器中。

我们引用的b实际上是那个临时变量的。

验证:

可见虽然b是a的引用,但是地址是不同的。

这里也可以得知:临时变量是具有常性的

6.4使用场景

1.做参数

使用引用可以规避指针问题

初始C语言时:写过交换函数

void Swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a ,b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	return 0;
}

这个写法是错误的

交换了临时变量但实参却没有解决。但引用就可以完美解决。

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 1, b = 2;
	Swap(a ,b);
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	return 0;
}

 

2.做返回值

函数返回值在出了作用域时,会销毁栈帧,原先的返回值会放在寄存器中(拷贝)

最后才会将结果赋值给你所创建在调用函数接口的变量中(拷贝)

int& f()
{
	static int a = 0;
	a++;
	return a;
}
int main()
{
	int b = f();
	return 0;
}

传值返回:会出现一个拷贝。

传引用返回:没有这个拷贝了,函数返回的直接就是返回变量的别名。

这似乎能提高计算机的速度,但是有一个致命的问题,引用的原对象必须

”活着“,否则这个引用就会越界访问,是违法的。

这里使用了static将a放进了静态区,生命周期为整个程序的生命周期,当然不会出现越界访问。

体验临时变量生命周期结束的情况:

int& Add(int a, int b)
{
	int c = 0;
	c = a + b;
	return c;
}
int main()
{
	int& ret = Add(1, 2);//ret为c的别名
	Add(3, 4);//再次调用函数,返回是被修改的c
	cout << "Add(1,2) : " << ret << endl;//第一次打印
	cout << "Add(1,2) : " << ret << endl;//第二次打印时,原先的c的栈帧已被销毁。
	cout << "Add(1,2) : " << ret << endl;//同第二次
	return 0;
}

 

所以:如果函数返回时,出了函数作用域,如果返回对象还未交还给系统,则可以使用引用返回,

如果已经返回给系统里,那就必须使用传值返回。

6.5针对值,指针和引用最返回值类型在性能上的比较

#include<time.h>
typedef struct Test
{
	int Arr[100000];
}ST;
ST st;
ST test1()
{
	return st;
}
ST& test2()
{
	return st;
}
ST* test3()
{
	return &st;
}
int main()
{
	//值返回
	size_t begin1 = clock();
	for (int i = 0; i < 10000; i++)
	{
		test1();
	}
	size_t end1 = clock();

	//引用返回
	size_t begin2 = clock();
	for (int i = 0; i < 10000; i++)
	{
		test2();
	}
	size_t end2 = clock();

	//指针返回
	size_t begin3 = clock();
	for (int i = 0; i < 10000; i++)
	{
		test3();
	}
	size_t end3 = clock();

	cout << "值返回:" << end1 - begin1 << endl;
	cout << "引用返回:" << end2 - begin2 << endl;
	cout << "指针返回:" << end3 - begin3 << endl;
	return 0;
}

 

通过上述比较,可知指针返回和引用返回的效率差不多,值返回效率最差。

6.6引用和指针的区别

引用:在概念上引用只是原变量的一个别名,没有独立的空间,和引用实体公用同一块空间。

指针:要开辟空间,为4/8字节。

而底层实现上,引用是有空间的,因为引用是按照指针的方式实现的。

引用

指针

初始化

必须初始化

无要求

指向性

不能引用多个实体

可指向任一实体

NULL

无NULL引用

NULL指针

sizeof含义

引用类型的大小

指针地址空间所占字节个数

自加

实体增加1

向后偏移一个类型的大小

多级

无多级引用

有多级指针

访问实体方式

编译器自行处理

需要解引用

安全性

引用比指针使用起来更加安全

查看引用和指针的汇编代码

7.内联函数

7.1概念

以inline修饰的函数叫做内联函数,编译时C++编译器会在掉哦用内联函数的地方展开。

没有函数压栈的一系列消耗,内联函数可极大的提高效率。

根据内联函数的基本概念,可知其与C语言中的宏有相似之处。

对比C语言和C++内联函数

宏定义Add函数
#define ADD(X,Y) ((X) + (Y))

int main()
{
	ADD(1, 2);
	cout << ADD(1, 2) << endl;
	return 0;
}
int main()
{
	int ret = ADD(1, 2);
	cout << ret << endl;
	return 0;
}
//内联函数
inline int ADD(int x, int y)
{
	return x + y;
}
int main()
{
	int ret = ADD(1, 2);
	cout << ret << endl;
	return 0;
}

 

7.2特性

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

2.inline对于编译器是一种建议,所以如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略内联。

3.inline不推荐声明和定义分离,分离会导致连接错误。因为inline被展开就没有函数地址了,链接时就会报错。

C++推出内联函数是为了解决C语言宏的晦涩难懂,容易出错,不方便调试的缺点。

优点:满足C语言宏的所有优点,

内联的写法与普通函数完全相同,极大的减少了编写的工程量。

【面试题】

宏的优缺点?

优点

缺点

增强代码的可读性

不方便调试宏(预编译阶段宏会被替换)

提高性能

代码可读性变差,维护性变差,易被误用

没有类型安全的检查

C++针对C语言宏的优化替代?

1.常量定义 换用const

2.函数定义 换用内联函数

8.auto关键字

8.1auto简介

在C/C++赋予auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,但随着编译器的优化

auto似乎没再出现过。

C++11中,auto被赋予了全新的含义:auto不再是一个储存类型的指示符,而是作为一个新的

类型的指示符来指示编译器,auto生命的变量必须由编译器在编译时期推导而得。

int main()
{
	int a = 10;
	auto b = a;//int 
	auto c = 'a';//char
	auto d = &a;//int*
	auto* e = &a;//int*
	auto& f = a;//int
	//typeid(a).name();用于查看变量类型
	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;
	cout << typeid(f).name() << endl;
	//d = 20;
	return 0;
}

 

注意: auto g;非法,g的类型无法被识别。

【注意】使用auto定义变量时必须对其进行初始化,在编译阶段需要根据初始化表达式来

推导auto的实际类型。因此auto并非是一种“类型”的声明,而是一个类型声明时的占位符,

编译器在预编译期将auto替换为变量实际的类型。

那auto是否可以作为缺省参数进行传参?

void AutoTest(auto a = 10)
{}
int main()
{
	AutoTest();
	return 0;
}

答案是不允许的。

8.2auto的使用细则

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

用auto声明指针类型时,用auto或auto*没有任何区别,但用auto声明引用变量时必须加&

int main()
{
	int a = 10;
	auto b = &a;
	auto* c = &a;
	auto& x = a;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(x).name() << endl;
	return 0;
}

 

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

在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器会报错。

原因:编译器实际只会对第一个类型进行推导,然后用推导出来的类型定义其他变量。

测试:

void AutoTest()
{
	auto a = 10, b = 20;
	auto c = 10.0, d = 20;
}
int main()
{
	AutoTest();
	return 0;
}

 

8.3auto不能推导的场景

  • auto不能作为函数参数
  • auto不能用来声明数组
  • C++11只保留了auto作为类型指示符的用法
  • auto最常用的是配合范围for循环,和lambda表达式配合使用。

9.基于范围的for循环(C++11)

9.1范围for的语法

以遍历数组为例:

C++98语法规则遍历

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	for (int i = 0; i < sizeof(a)/sizeof(int); i++)
	{
		cout << a[i] << " ";
	}
	cout << endl;
	return 0;
}

C++11语法规则范围for遍历

int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	for (auto e : a)//范围for (auto 变量 : 数组名}
	{
		cout << e << " ";
	}
	cout << endl;
	return 0;
}

 对于一个有范围的集合而言,使用范围for时比较优的选择。

for循环后的括号由冒号”:“分为两部分:一部分时范围内用于迭代的变量,

第二部分则表示被迭代的范围。

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

9.2范围for的使用条件

1.for循环的迭代的范围必须是确定的。

对数组而言,就是数组中第一个元素和最后一个元素的范围;

对类而言,提供beginend的范围,beginend就是for循环迭代的范围。

错误示范:

void ForTest(int a[])
{
	for (auto e : a)
	{
		cout << e << " " << endl;
	}
	cout << endl;
}
int main()
{
	int a[] = { 1,2,3,4,5,6,7,8,9 };
	ForTest(a);
 return 0;
}

 

原因:数组传参传递的是首元素地址,没有直接可确定的范围,自然范围for就是错误的。

2.迭代器的对象实现++和==的操作。

10.指针空值nullptr(C++11)

10.1C++98中的指针空值

在C++98中可以这样定义空指针

NULL实际上是一个宏,包含在C头文件(stddef.h)中,转到定义可知:

NULL可能被定义为字面常量0或者被定义为(void*)常量,但这可能会出现指代不明的情况。

void NULLTest(int)//字面常量
{
	cout << "int" << endl;
}
void NULLTest(int*)//(void*)常量
{
	cout << "int*" << endl;
}
int main()
{
	NULLTest(0);//~int
	NULLTest(NULL);//希望调用int*
	NULLTest((int*)NULL);//~~int*
	NULLTest(nullptr);
	return 0;
}

 

可以看到,事与愿违,NULL调用的是int类型的字面常量。

在C++98中,字面常量既可以是一个整型数字,也可以是个无类型的指针(void*)常量,

编译器默认情况下将其看成一个整型常量,如果要按照指针的方式来使用,必须对其使用

强转。

C++11推出了新的nullptr来替代原有的NULL/0.

注意:

1.在使用nullptr表示指针为空时,不与要包含头文件,因为nullptrC++11版本下是关键字。

2.C++中,sizeofnullptr)与sizeof((void*))所占字节相同。

3.为了提高代码的健壮性,nullptrc++中可以完全替代NULL/0.

如有错误,还请大佬指出!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值