C++入门(学习笔记)

一.认识命名空间

1.命名空间的作用

在C/C++中,变量、函数和后面要学到的类都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。使用命名空间的目的是对标识符的名称进行本地化,以避免命名冲突或名字污染,namespace关键字的出现就是针对这种问题的。

2.命名空间定义

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

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中

在这里插入图片描述
2.命名空间可以进行嵌套
在这里插入图片描述

  1. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
    在这里插入图片描述
3.命名空间的使用

如何正确使用命名空间

#pragma once
#include<stdio.h>
namespace M1
{
	int a=10;
	int b=20;
	int e=30;
}
int main()
{
	printf("%d\n",a);
	return 0;
}

这里的代码运行会报错:
在这里插入图片描述
正确使用命名空间的方法:
1,加命名空间名称及作用域限定符

	printf("%d\n",M1::a);

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

using M1::b;
int main()
{
	printf("%d\n",M1::a);
	printf("%d\n",b);
	return 0;
}

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


using namespace M1;

int main()
{
	printf("%d\n",a);
	printf("%d\n",b);
	return 0;
}

二.C++的输入&输出(初识c++)

c++输出helloworld!

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

说明:

  1. 使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含头文件以及std标准命名空间
    注意:早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。
    2.使用C++输入输出更方便,不需增加数据格式控制,比如:整形–%d,字符–%c
#include<iostream>
using namespace std;
int main()
{
	int a;
	double b;
	char c;
	cin >> a;
	cin >> b >> c;
	cout << b<<""<<c<< endl;
	return 0;
}

cin类似与C语言中的scanf,在控制台输入.
std::cout 和 std::endl 与C语言中的printf类似,在 C++ 中用于输出数据到标准输出.

三. 缺省参数

1.缺省参数的概念

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

缺省参数就是函数传参的备胎,函数传参是有参数,函数定义中参数指定的值不使用,当使用函数是没有参数时采取函数定义中参数指定的值.
在这里插入图片描述

2. 缺省参数的分类

1.全缺省参数

void Test(int a = 1, int b = 3, int c = 5)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

2.半缺省参数

void Test(int a , int b = 3, int c = 5)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

注意:

  1. 半缺省参数必须从右往左依次来给出,不能间隔着给
  2. 缺省参数不能在函数声明和定义中同时出现
  3. 缺省值必须是常量或者全局变量
  4. C语言不支持(编译器不支持)

四.函数重载

1.函数重载的概念

函数重载:是指在同一作用域内,可以有一组具有相同函数名,不同参数列表的函数,这组函数被称为重载函数。重载函数通常用来命名一组功能相似的函数,这样做减少了函数名的数量,避免了名字空间的污染,对于程序的可读性有很大的好处。

 在支持函数重载的编程语言中(如C++、C#等),***构成函数重载***需要满足以下条件:

相同的函数名:重载的函数必须具有相同的名字。
不同的参数列表:重载的函数必须有不同的参数列表,这可以通过以下方式实现:
参数数量不同:一个函数可以有三个参数,而另一个同名函数可以有四个参数。
参数类型不同:一个函数的参数可以是整数类型,而另一个同名函数的参数可以是浮点类型。
参数顺序不同:如果参数类型相同,但顺序不同,也可以构成重载(但这在C++中通常不是一个好的实践,因为它可能导致混淆)。
下面是几个构成函数重载的例子:

// 例子1:参数数量不同
void Print(int a);
void Print(int a, int b);

// 例子2:参数类型不同
void Add(int a, int b);
void Add(double a, double b);

// 例子3:参数顺序不同(不推荐,可能导致混淆)
void Func(int a, double b);
void Func(double a, int b);

以下是一些不构成函数重载的情况:

仅返回类型不同:仅返回类型不同的函数不能构成重载。例如,以下两个函数不构成重载:

int Add(int a, int b);
double Add(int a, int b); // 错误,不构成重载

仅参数名称不同:参数名称的变化不会影响函数的重载,只有参数类型和数量会影响:

void Func(int a, int b);
void Func(int x, int y); // 错误,不构成重载

2.名词修饰

C++支持函数重载,而C语言不支持,这主要是由于两者在编译时的符号决议(name mangling)和链接机制上的差异。

以下是几个关键点解释了为什么C++支持函数重载,而C语言不支持:

名称修饰(Name Mangling):

C++:在C++中,编译器使用一种称为名称修饰(name
mangling)的技术来为每个函数生成唯一的名字。这个过程涉及到将函数的名称、参数类型、参数顺序以及所在的作用域等信息编码成一个唯一的字符串。这样,即使函数名称相同,由于参数类型或数量不同,编译后的符号也会不同,从而允许函数重载。
C:C语言不支持名称修饰。在C中,函数的名称在编译后的符号表中是固定的,不包含参数信息。因此,如果两个函数有相同的名字,即使它们的参数列表不同,编译器也无法区分它们,这会导致链接时的冲突。

编译和链接机制:

C++:C++的编译模型允许编译器在编译时处理函数重载,通过名称修饰来保证每个重载函数都有一个唯一的标识符。
C:C语言的编译模型相对简单,编译器生成的符号表中只包含函数的名称,不包含参数信息。因此,在链接时,如果出现同名函数,链接器无法确定应该使用哪个函数,从而产生错误。

类型检查:

C++:C++是一种强类型语言,它允许编译器在编译时进行更严格的类型检查,这为函数重载提供了支持。
C:虽然C也是一种强类型语言,但它的类型检查不如C++严格,因为它不支持重载,所以不需要在编译时考虑参数类型来区分同名函数。

五.引用

1.引用的概念

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:李逵,在家称为"铁牛",江湖上人称"黑旋风"。

引用的使用: 类型& 引用变量名(对象名) = 引用实体;
在这里插入图片描述
注意:引用类型必须和引用实体是同种类型的

2.引用的特性

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

#include<iostream>
using namespace std;
void Test()
{
	int a = 20;
	int b = 20;
	int& q = a;    //定义引用类型
	int& w = a;
	int& q = b;
}
int main()
{
	Test();        
	return 0;
}

3.常引用

在C++中,“常引用”(const reference)是一种引用类型,它用于引用一个常量。这意味着通过这个引用不能修改所引用的对象的值。常引用通常用于以下两种情况

1.当需要传递大型对象(如大型结构体或类对象)到函数中,而又不想复制该对象时,可以使用引用来避免复制。如果不需要在函数内部修改这个对象,就可以使用常引用。

2.当函数的参数需要被指定为常量,确保在函数内部不会修改该参数时。

常引用的声明方式是在引用的类型前加上const关键字。例如:

const Type& name;
Type是任意类型,name是引用的名称。

 void TestConstRef()
 {
   const int a = 10;
   //int& ra = a;   // 该语句编译时会出错,a为常量
   const int& ra = a;
   // int& b = 10;  // 该语句编译时会出错,b为常量
   const int& b = 10;
   double d = 12.34;
   //int& rd = d;  // 该语句编译时会出错,类型不同
   const int& rd = d;
   }

const引用的一些规则和限制:

1.您不能将一个普通的引用绑定到一个常量上,因为引用需要能够指向一个可修改的左值。这就是为什么int& ra = a;会编译出错,因为a是一个常量。

2.您可以将一个const引用绑定到一个常量上。这就是为什么const int& ra = a;是合法的。通过这种方式,您可以引用常量,但是不能通过引用来修改它。

3.您不能将一个普通的引用绑定到一个字面量上,因为字面量是不可修改的。这就是为什么int& b = 10;会编译出错,因为10是一个字面量。

4.但是,您可以将一个const引用绑定到一个字面量上。这就是为什么const int& b = 10;是合法的。在这种情况下,编译器通常会创建一个临时的、不可修改的对象来存储字面量的值,并将引用绑定到这个临时对象上。

5.您不能将一个引用绑定到一个不同类型的对象上,除非进行显式类型转换。这就是为什么int& rd = d;会编译出错,因为d是一个double类型,而rd是一个int引用。

6.但是,您可以绑定一个const引用到一个不同类型的对象上,这将允许类型转换。这就是为什么const int& rd = d;是合法的。在这种情况下,编译器会创建一个临时的int对象,将d的值转换为int,并将引用绑定到这个临时对象上。

[注意]

绑定const引用到不同类型的对象时,可能会发生数据丢失

4.引用和指针的区别

  1. 引用在定义时必须初始化,指针没有要求
  2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
  3. 没有NULL引用,但有NULL指针
  4. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)
  5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
  6. 有多级指针,但是没有多级引用
  7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
  8. 引用比指针使用起来相对更安全

六.内联函数

1.概念

在C++中,内联函数是一种建议编译器以函数体中的代码替换函数调用的特殊函数。这意味着,当程序调用一个内联函数时,编译器会尝试将函数的代码直接插入到调用点,而不是生成一个函数调用的指令。这样可以减少函数调用的开销,特别是对于那些频繁调用但函数体很小的函数。

inline returnType functionName(parameters) {
    // 函数体
}

在这里插入图片描述
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用。
查看方式:

  1. 在release模式下,查看编译器生成的汇编代码中是否存在call Add
  2. 在debug模式下,需要对编译器进行设置,否则不会展开(因为debug模式下,编译器默认不会对代码进 行优化,以下给出vs2013的设置方式)
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

2.特性

1.节省函数调用的开销:由于内联函数在调用时不需要进行函数调用的栈操作,因此可以节省入栈和出栈的开销。

2.增加代码体积:由于内联函数的代码在每个调用点都会被插入,所以可能会增加最终生成的可执行文件的体积。

3.适用于简单函数:通常,只有那些非常简单、执行速度快的函数才适合被声明为内联函数。复杂的函数如果被声明为内联,可能会导致代码体积显著增加。
4.内联宏:内联函数可以看作是宏的一种更安全的替代品,因为宏仅仅是文本替换,而内联函数会进行类型检查和参数推导。

5.内联限制:有些情况下,函数不能被内联,比如递归函数、包含循环的函数、包含静态局部变量的函数等。
6.内联模板函数:模板函数通常在头文件中定义,并且默认是内联的,因为它们的实现必须在每个使用点可见,以便编译器能够实例化模板。

[注意]

内联函数时,应当谨慎,因为过度使用内联函数可能会导致程序的性能下降,尤其是在函数体较大或者调用频率不高的情况下。正确的使用内联函数可以提升程序的运行效率。

【面试题】

   宏的优缺点? 优点:

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

 C++有哪些技术替代宏?
  1. 常量定义 换用const
  2. 函数定义 换用内联函数

七.auto关键字

1.auto关键词简介

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

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

[注意]

使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类
型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为 变量实际的类型。

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

2. 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对
第一个类型进行推导,然后用推导出来的类型定义其他变量。

void TestAuto()
{
 auto a = 1, b = 2; 
 auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

7.3auto关键字不可推导的场景

  1. auto不能作为函数的参数
// 此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{

}
  1. auto不能直接用来声明数组
void TestAuto()
{
 int a[] = {1,2,3};
 auto b[] = {456};
}
  1. 为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
  2. auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。

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

1.范围for的语法

基于范围的for循环的基本语法如下:

for (declaration : expression)
{
// 循环体
}

declaration是用于在每次迭代中存储序列中当前元素的变量,而expression是提供要迭代的序列的表达式。
示例:


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

[注意]

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

2.范围for的使用条件

基于范围的for循环的使用条件如下:

序列必须支持迭代器:要迭代的序列必须支持迭代器,这意味着它必须至少提供了begin()和end()成员函数,或者支持非成员函数begin()和end()的重载。

声明必须是可修改的左值:declaration必须是一个可修改的左值,或者是一个引用。如果只需要读取元素而不修改它们,可以使用const引用。

类型推断:如果使用auto关键字,编译器会自动推断declaration的类型。

示例:

//声明并初始化了一个名为vec的std::vector对象,该对象包含五个int类型的元素,分别为1, 2, 3, 4, 和 5。
std::vector<int> vec = {1, 2, 3, 4, 5};

// 基本使用
for (int value : vec) {
    std::cout << value << ' ';
}

// 使用 auto 和 引用
for (auto& element : vec) {
    element *= 2; // 直接修改容器中的元素
}

// 使用 const 引用
for (const auto& element : vec) {
    std::cout << element << ' '; // 读取元素,但不修改它们
}

// 对于数组
int arr[] = {1, 2, 3, 4, 5};
for (int value : arr) {
    std::cout << value << ' ';
}

// 对于字符串
std::string str = "Hello, World!";
for (char ch : str) {
    std::cout << ch;
}

[注意]

1.如果需要修改序列中的元素,应该使用引用(例如auto&)。
2.如果不需要修改元素,使用const引用(例如const auto&)可以提高效率,因为它避免了不必要的拷贝。
3.对于基本数据类型(如int、char等),如果不修改元素,可以直接在循环中声明它们,因为这将创建元素的副本。

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

1.nullptr的简介

在C++中,nullptr是C++11标准引入的一个关键字,用于表示空指针值。在nullptr被引入之前,C++程序通常使NULL或直接使用数字0来表示空指针。然而,nullptr的引入提供了一种更安全和更清晰的表示空指针的方法.

2.nullptr的特点

1.类型安全:nullptr具有一个特殊的类型std::nullptr_t,它不是整数类型,也不是指针类型,但是可以隐式转换为任何指针类型和任何整数类型(尽管将nullptr转换为整数类型通常不是一个好的做法)。

2.与NULL区分:在C++中,NULL通常被定义为整数0。这可能导致潜在的问题,因为0可以是一个整数值,也可以是一个空指针值,而nullptr仅表示空指针值。

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

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

NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量.不论采取何种定义,在使用空值的指针是,都不可避免的会遇到一些麻烦.如下:

void f(int)
{
 cout<<"f(int)"<<endl;
}
void f(int*)
{
 cout<<"f(int*)"<<endl;
}
int main()
{
 f(0);
 f(NULL);
 f((int*)NULL);
 return 0;
}

程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。

3.重载决议:当使用nullptr时,它可以更好地参与函数重载决议。例如,如果你有两个重载函数,一个接受int参数,另一个接受指针参数,传递nullptr将明确调用指针版本的函数,而传递NULL或0可能会调用整数版本的函数。

[注意]

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

3.nullptr与NULL和0的比较

NULL:在C++中,NULL通常被定义为((void*)0)或直接为0。这意味着NULL是一个整数常量,可以隐式转换为任何指针类型。

0:数字0也可以被用作空指针,但是它是一个整数值,这可能会导致混淆,尤其是在函数重载的情况下。

nullptr:nullptr是一个关键字,专门用于表示空指针,并且具有类型std::nullptr_t。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值