C++入门基础

1. C++的发展历史

1970年,AT&T贝尔实验室的工作人员D.Ritchie和K.Thompson共同研发了C语言。研制C语言的初衷是用它编写UNIX系统程序,因此,实际上C语言是UNIX的“副产品”。
1971年,瑞士联邦技术学院N.Wirth教授发明了第一个结构化的编程语言Pascal。
20世纪70年代中期,本贾尼·斯特劳斯特卢普在剑桥大学计算机中心工作。斯特劳斯特卢普希望开发一个既要编程简单、正确可靠,又要运行高效、可移植的计算机程序设计语言。而以C语言为背景,以Simula思想为基础的语言,正好符合斯特劳斯特卢普的初衷和设想。
1979年,本贾尼·斯特劳斯特卢普到了AT&T贝尔实验室,开始从事将C改良为带类的C(C with classes)的工作。、1983年,该语言被正式命名为C++。
1985年、1990年和1994年,C++先后进行3次主要修订。
C++的标准化工作于1989年开始,并成立了一个ANSI和ISO(International Standards Organization)国际标准化组织的联合标准化委员会。
1994年1月25曰,联合标准化委员会提出了第一个标准化草案。在该草案中,委员会在保持斯特劳斯特卢普最初定义的所有特征的同时,还增加了部分新特征。
在完成C++标准化的第一个草案后不久,亚历山大·斯特潘诺夫(Alexander Stepanov)创建了标准模板库(Standard Template Library,STL)。在通过了标准化第一个草案之后,联合标准化委员会投票并通过了将STL包含到C++标准中的提议。STL对C++的扩展超出了C++的最初定义范围。虽然在标准中增加STL是个很重要的决定,但也因此延缓了C++标准化的进程。
1997年11月14日,联合标准化委员会通过了该标准的最终草案。
1998年,C++的ANSI/IS0标准被投入使用

2. C++版本更新

在这里插入图片描述

3. C++参考文档

https://legacy.cplusplus.com/reference/
https://zh.cppreference.com/w/cpp
https://en.cppreference.com/w/
说明:第⼀个链接不是C++官⽅⽂档,标准也只更新到C++11,但是以头⽂件形式呈现,内容⽐较易看好懂。后两个链接分别是C++官⽅⽂档的中⽂版和英⽂版,信息很全,更新到了最新的C++标准,但是相⽐第⼀个不那么易看;

4. C++的第一个程序

C++兼容C语⾔绝⼤多数的语法,所以C语⾔实现的helloworld依旧可以运⾏,C++中需要把定义⽂件代码后缀改为.cpp,vs编译器看到是.cpp就会调⽤C++编译器编译,linux下要⽤g++编译,不再是gcc

#include<stdio.h>
int main()
{
printf("hello world\n");
return 0;
}

当然C++有⼀套⾃⼰的输⼊输出,严格说C++版本的helloworld应该是这样写的.

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

5. 命名空间

5.1 namespace 的价值

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

c语⾔项⽬类似下⾯程序这样的命名冲突是普遍存在的问题,C++引⼊namespace就是为了更好的解决
这样的问题。

#include<stdio.h>
#include<stdlib.h>
int rand = 10;

int main()
{
	//编译报错:error C2365: “rand”: 重定义;以前的定义是“函数”
	printf("%d\n",rand);
	return 0;
}

5.2 namespace的定义

  • 定义命名空间,需要使⽤到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中
    即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
  • namespace本质是定义出⼀个域,这个域跟全局域各⾃独⽴,不同的域可以定义同名变量,所以下
    ⾯的rand不在冲突了。
  • C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/
    类型出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。局部域和全局域除了会影响
    编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
  • namespace只能定义在全局,当然他还可以嵌套定义。
  • 项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
  • C++标准库都放在⼀个叫std(standard)的命名空间中。
    当我们使用命名空间之后上述问题问题就可以得到很好的解决
#include<stdio.h>
#include<stdlib.h>

namespace learn
{
	int rand = 10;
}

int main()
{
	printf("%d\n", learn::rand);

	return 0;
}

5.3命名空间的使用

编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以
下⾯程序会编译报错。所以我们要使⽤命名空间中定义的变量/函数,有三种⽅式:

1.指定命名空间访问

namespace A
{
	int a = 0;
	int b = 1;
}

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

2.使用using将命名空间中的某个成员展开

像下面这种情况下a经常使用可以单独将a展开

namespace B
{
	int a = 0;
	int b = 1;
}

using B::a;

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

3.使用using展开命名空间中的全部成员

namespace C
{
	int a = 0;
	int b = 1;
}

using namespace C;

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

6. C++的输入输出

C++的输入输出分别是cin和cout ,我们在使用C++的标准输入输出流时需要注意一些东西:

  • 使用cout 和cin时必须包含头文件并且按照命名空间的使用方式使用std。
  • cin 是istream类的对象,它主要⾯向窄字符(narrowcharacters(oftypechar))的标准输⼊流。
  • cout 是ostream类的对象,它主要⾯向窄字符的标准输出流。
  • endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换⾏字符加刷新缓冲区。
  • <<是流插⼊运算符,>>是流提取运算符。(C语⾔还⽤这两个运算符做位运算左移/右移)
  • 使⽤C++输⼊输出更⽅便,不需要像printf/scanf输⼊输出时那样,需要⼿动指定格式,C++的输⼊
    输出可以⾃动识别变量类型
  • 这⾥我们没有包含<stdio.h>,也可以使⽤printf和scanf,在包含间接包含了。vs系列
    编译器是这样的,其他编译器可能会报错。
#include<iostream>

using namespace std;

int main()
{
	int a = 0;
	int b = 0;
	cin >> a;
	scanf("%d",&b);
	
	cout << a << endl;
	printf("%d\n",b);
}
1.在日常练习中,建议使用using namespace std; 这样更方便使用.
2.using namespace std;会将标准库全部暴露出来,如果我们定义和库重名的类型、对象、函数就存在冲突问题。在项目开发中代码规模大很容易出
现这种情况所以建议在项目中使用std::cout或using std::cout;这两种使用方式

7. 缺省参数

7.1缺省参数的概念

缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参
则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数.

#include<iostraem>
using namespace std;

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

int main()
{
	learn();		//没有传参时使用参数的默认值
	learn(1);		//传参时使用指定的实参
	return 0;
}

7.2缺省参数的分类.

1.全缺省参数(全部形参给缺省值)

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

int main()
{
	learn();
	
	learn(1);
	learn(1,2);
	learn(1,2,3);
	
	return 0;
}

2.半缺省(部分形参给缺省值)

void learn(int a, int b = 20, int c = 30)
{
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
}

int main()
{
	learn(1);
	learn(1,2);
	learn(1,2,3);
	
	return 0;
}

注意

      1.C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
      2.带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
      3.函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。

8. 函数重载

8.1 函数重载的概念

C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者
类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同
名函数的。

8.2 函数重载的类型

1.参数类型不同

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

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

int main()
{
	Add(1,1);
	Add(1.1,2.2);
	return 0;
}

2.参数个数不同

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

void A(int a, double b)
{
	cout << "A(int a, double b)" << endl;
}

3.参数类型的顺序不同

void A(int a, double b )
{
	cout << "A(int a, double b)" << endl;
}

void A(double a, int b)
{
	cout << "A(double a, int b)" << endl;
}

注意:

  1.如果两个函数,函数名和参数一样但是返回值不同,是不构成重载的。
  2.下面两个函数构成重载,但是调用时会报错存在歧义
void f1()
{
	cout << "f()" << endl;
}
void f1(int a = 10)
{
	cout << "f(int a)" << endl;
}

9.引用

9.1 引用的概念和定义

引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,
它和它引⽤的变量共⽤同⼀块内存空间。
类型& 引⽤别名=引⽤对象;

  注意:C++中为了避免引⼊太多的运算符,会复⽤C语⾔的⼀些符号,⽐如前⾯的<<和>>,这⾥引⽤也和取地
  址使⽤了同⼀个符号&,⼤家注意使⽤⽅法⻆度区分就可以。

在这里可以看到a,b,c,d四个变量使用的是同一块内存

在这里可以看到a,b,c,d四个变量使用的是同一块内存

9.2 引用的特性

  • 引用在定义时必须初始化 int& ra不成立
  • 一个变量可以有多个引用
  • 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体
int main()
{
	int a = 0;
	int& ra = a;

	int b = 1;
	//这里并非让ra引用b,因为C++引用不能改变指向
	//这里是赋值
	ra = b;
}

9.3 引用的使用

  • 引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象。
  • 引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些.
  • 引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。C++的引⽤跟其他语⾔的引⽤(如Java)是有很大的区别的,除了⽤法,最⼤的点,C++引⽤定义后不能改变指向,Java的引⽤可以改变指向。

9.4 const引用

const引用是指在引用类型前加上const关键字,此处和const修饰变量一样被const修饰的引用时不能修改引用变量的值的
可以引⽤⼀个const对象,但是必须⽤const引⽤。这里我们需要注意一个问题,那就是引用的权限问题, const引⽤也可以引⽤普通对象,因为对象的访问权限在引⽤过程中可以缩⼩,但是不能放⼤。

1.权限保持

int main()
{
	//权限保持
	const int a = 0;
	const int& b = a;
	return 0;
}
此处我们可以看到并没有产生报错,因为a被const修饰所有a就只有可读的权限,当我们使用只有可读权限的b去给a取别名引用时
没有任何错误这属于权限的平移

2.权限放大

int main()
{
	const int a = 0;
	int& b = a;
	return 0;
}
此处a被const修饰只有可读的权限但是让b给a取别名引用时b具有写的权限属于权限的放大但对象的访问权限在引用时不
能放大所以会报错

3.权限缩小

int main()
{
	int a = 0;
	const int& b = a;
	return 0;
}
  此处a并没有被const修饰所以a可读可写,用被const修饰的b给a取别名时b就只有读的权限所以时权限缩小。

9.5指针和引用的关系

  • 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间。
  • 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的。
  • 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
  • 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
  • sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下
    占4个字节,64位下是8byte)
  • 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些.

10. 内联函数

10.1内联函数的概念

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联
函数就需要建⽴栈帧了,就可以提⾼效率。

我们知道在函数调用时会建立栈帧,而如果频繁的调用一个特别小的函数时,一定会使效率降低,在C语言中可以使用宏函数来解决,但宏又有许多缺点

- 不能调试
- 没有类型安全检查
- 有些场景下非常复杂,容易出错
#define Add(x,y) ((x)+(y))

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

而使用内联函数后

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

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

C语言宏函数
在这里插入图片描述

C语言函数调用
在这里插入图片描述

C++内联函数

在这里插入图片描述

10.2内联函数的特性

  • inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展
    开,不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁
    调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
  • C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调
    试,C++设计了inline⽬的就是替代C的宏函数。
  • inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地
    址,链接时会出现报错。

11. nullptr

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

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

C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种
定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的
f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。f((void*)NULL);
调⽤会报错。

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

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

int main()
{
	f(0);
	//本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(intx),因此与程序的初衷相悖。
	f(	NULL);
	f((int*)NULL);
	// 编译报错:error C2665: “f”: 2 个重载中没有⼀个可以转换所有参数类型
	//f((void*)NULL);
	f(nullptr);
	
	return 0;
}

C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换
成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被
隐式地转换为指针类型,⽽不能被转换为整数类型。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐