C++初阶入门

C++初级入门

1.C++关键字

C++共计63个关键字,而C语言有32个关键字

其实大多数都是C语言中的,常用的那些记住即可

image-20220120143603625

2.命名空间

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

2.1命名空间的定义

namespace+名字,后接一对{}

//例如
namespace N
{
    
    }

命名空间的特点

1、在命名空间内,既可以定义变量,也可以定义函数

2、命名空间可以嵌套

3、同一个工程中允许同时存在多个相同的命名空间,编译器最后会合成同一命名空间

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

//1.命名空间中的内容,既可以定义变量,也可以定义函数
namespace N1 
{
    
    
	int a;
	int Add(int left, int right)
	{
    
    
		return left + right;
	}
}
//2. 命名空间可以嵌套
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;
		}
	}
}
//3. 同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace N1
{
    
    
	int Mul(int left, int right)
	{
    
    
		return left * right;
	}
}

2.2命名空间的使用

看下面的代码我们该如何使用命名空间呢?

namespace N 
{
    
    
	int a = 10;
	int b = 20;
	int Add(int left, int right)
	{
    
    
		return left + right;
	}
	int Sub(int left, int right)
	{
    
    
		return left - right;
	}
}

命名空间的三种使用方式

首先介绍一下**"::"**这个双冒号,叫做域作用限定符,可以用来访问命名空间中的成员

  • 加命名空间名称和域作用限定符

    int main()
    {
          
          
     printf("%d\n", N::a);
     return 0; 
    }
    //这样做到了最好的隔离使用,但是使用不方便
    
  • 使用using将命名空间中的成员引入

    using N::b;
    int main()
    {
          
          
     printf("%d\n", N::a);
     printf("%d\n", b);
     return 0; 
    }
    //单独展开某个成员,展开常用的
    
  • 使用using namespace 全部引入

    using namespce N;
    int main()
    {
          
          
     printf("%d\n", N::a);
     printf("%d\n", b);
     Add(10, 20);
     return 0; 
    }
    //全部展开,用起来方便,但是原本的隔离失效了(慎用)
    //平时练习时可以全部展开,但写项目时应该严格展开要用的
    

    3.C++的输入和输出

    1、cin标准输入,">>"叫做流提取操作符,我们从键盘输入的信息,就好像流水一样流入变量中去了

    2、cout标准输出,"<<"叫做流插入操作符,我们在屏幕上获取的信息好像流水一样,流出到屏幕上去了

使用标准输入输出我们需要包含和std标准命名空间

可以类比C语言中的<stdio.h>,C++库的实现定义在了一个std的命名空间中,我们使用时需要将其展开

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

使用C++的输入输出更加方便,不需要增加数据格式控制,他能自动识别类型,比如%d,%s等

#include<iostream>
using namespace std;
int main()
{
    
    
    int a;
    double b;
    char c;
    cin>>a;
    cin>>b;
    cin>>c;
    cout<<a<<endl;
    cout<<b<<" "<<c<<endl;
    return 0;
}

4.缺省参数

4.1缺省参数的概念

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

//例如
void TestFunc(int a = 0)
{
    
    
	cout << a << endl;
}
int main()
{
    
    
	TestFunc(); // 没有传参时,使用参数的默认值
	TestFunc(10); // 传参时,使用指定的实参
}

4.2缺省参数的分类

  • 全缺省参数(就是函数全部给了默认值)

    void TestFunc(int a = 10, int b = 20, int c = 30)
    {
          
          
     cout<<"a = "<<a<<endl;
     cout<<"b = "<<b<<endl;
     cout<<"c = "<<c<<endl; 
    }
    
  • 半缺省参数(不是缺了一半,而是缺了部分

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

**注意:**1、半缺省参数值必须从右往左依次给,不能间隔

//这些是不行的
void TestFunc(int a,int b=20,int c)
{
    
    }
void TestFunc(int a=10,int b=20,int c)
{
    
    }

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

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

5函数重载

简单点说就是会出现,函数名相同,但是实现的功能却不同,C++中允许这种情况出现

5.1函数重载的概念

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

//例如
int Add(int a,int y)
{
    
    
    return x+y;
}
double Add(double x,double y)
{
    
    
    return x+y;
}
//函数名字相同,但参数列表却不同

5.2函数重载原理—名字修饰

编译器在编译.cpp文件中当前使用的作用域里的同名函数时,根据函数形参的类型和顺序会对函数进行重命名(不同的编译器在编译时对函数的重命名标准不一样)但是总的来说,他们都把文件中的同一个函数名进行了重命名;

  • 在vs编译器中:

根据返回值类型(不起决定性作用)+形参类型和顺序(起决定性作用)的规则重命名并记录在map文件中。

  • 在linux g++ 编译器中:

根据函数名字的字符数+形参类型和顺序的规则重命名记录在符号表中;从而产生不同的函数名,当外面的函数被调用时,便是根据这个记录的结果去寻找符合要求的函数名,进行调用;

简单来说就是编译器将函数重新命名了,它能用特定的规则去识别同名函数

C语言是不支持函数重载的

5.3 extern"C"

有时候我们写的C++程序需要使用C来调用,C程序需要C++来调用,这时候就需要extern"C"

在函数的前面加上extern"C",就告诉编译器,要用C的规则来编译

6引用

6.1引用的概念

引用就是取别名,编译器不会为引用变量开辟内存空间,它和它的引用变量共用同一块内存空间

比如:水浒中的李逵,在家称为“铁牛”,在江湖人称“黑旋风”

6.2引用的使用

类型& 引用变量名=引用实体

//例如
int a=0;
int& ra=a;

6.3引用的特性

1、引用定义时必须初始化

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

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

6.4常引用

权限问题

  • const(具有常属性)修饰变量,使其具有只读的权限

  • 引用是读和写的权限

  • 类型转换时,会创建临时变量(同类型不创建),临时变量具有常属性

在引用取别名的过程中,权限可以缩小或者不变,但是不能放大

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

6.5使用场景

  • 作为参数
void Swap(int& left, int& right)
{
    
    
 int temp = left;
 left = right;
 right = temp;
}
  • 作为返回值
int& Count()
{
    
    
 static int n = 0;
 n++;
 return n; 
}

**注意:**如果函数返回时,出了函数作用域,返回对象还未还给系统,则可以使用引用返回;如果已经还给系统了,则必须使用传值返回。

6.6传值和传引用的效率

以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

6.7引用和指针的区别

在语法上:引用就是一个别名,没有开额外的空间,和其引用实体共用一块空间

在底层上:实际是由空间的,因为引用是按照指针的方式来实现的

引用和指针的不同点

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

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

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

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

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

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

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

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

7内联函数

7.1内联函数的概念

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

我们可以类比C语言中的来理解内联函数

我们可以利用下面的函数的汇编代码来对比普通函数和内联函数的区别,左边的函数是普通函数的汇编代码,右边是加上inline后的内联函数汇编代码,我们可以看到,内联函数并没有调用函数,只是将其展开了,没有额外开空间

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

int main()
{
    
    
	int ret = 0;
	ret = Add(3,5);
	return 0;
}

image-20220121102129063

7.2内联函数的特性

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

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

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

8 auto关键字

8.1 auto介绍

在C++11中,auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,它能够自动推导出数据的类型

image-20220121103647879

8.2 auto的使用

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

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

int main()
{
    
    
	int x = 10;
	auto a = &x;
	auto* b = &x;
	auto& c = x;
	return 0;
}

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

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

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

3.不能作为函数参数

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

4.不能用来直接声明数组

void TestAuto()
{
    
    
   int a[] = {
    
    1,2,3};
   auto b[] = {
    
    456};//这里会报错的
}

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

6.auto与C++11提供的新式for循环进行配合使用

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

我们要遍历一个数组现在可以这样写

void TestFor()
{
    
    
	int array[] = {
    
     1, 2, 3, 4, 5 };

	for (auto e : array)
		cout << e << " ";
}
int main()
{
    
    
	TestFor();
	return 0;
}
  • for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,

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

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

范围for的使用条件

1.for循环迭代的范围必须是确定的
 对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的方法,begin和end就是for循环迭代的范围。

2.迭代的对象要实现++和==操作
 关于迭代器的问题,以后再深入了解

10 nullptr(C++11)

10.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*)的常量。不论采取何种定义,在使用空值的指针时,都不可避免的会遇到一些麻烦,比如:

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,因此与程序的初衷相悖。

在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下

将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0。

nullptr总结

1、后续的指针空值推荐使用nullptr

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

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

C++初阶的入门的学习就到这里啦!

猜你喜欢

转载自blog.csdn.net/weixin_57675461/article/details/122617492