当前位置: 首页 > article >正文

C++基础(1)

目录

1. C++发展历史

2. C++第一个程序

3. 命名空间

3.1 namespace的价值

3.2 命名空间的定义 

3.3 命名空间的使用

4. C++输入和输出

5. 缺省参数 

6. 函数重载

6.1 实现函数重载的条件 

6.2 函数重载的应用


1. C++发展历史

C++的起源可以追溯到1979年,当时BjarneStroustrup(本贾尼·斯特劳斯特卢普,这个翻译的名字不同的地方可能有差异)在贝尔实验室从事计算机科学和软件工程的研究工作。面对项目中复杂的软件开发任务,特别是模拟和操作系统的开发工作,他感受到了现有语言(如C语言)在表达能力、可维护性和可扩展性方面的不足。

1983年,BjarneStroustrup在C语言的基础上添加了面向对象编程的特性,设计出了C++语言的雏形, 此时的C++已经有了类、封装、继承等核心概念,为后来的面向对象编程奠定了基础。这一年该语言被正式命名为C++。

随着C++的应用日益广泛,对其进行标准化的需求也越来越迫切。1990年,C++的标准化工作正式启动,由美国国家标准协会(ANSI)和国际标准化组织(ISO)共同负责。 

C++的标准化过程是一个充满挑战和争议的过程。在长达八年的时间里,来自不同国家和背景的开发者们就C++的语法、语义、库和运行时环境等方面进行了深入的讨论和协商。最终,在1998年,C++的第一个国际标准ISO/IEC 14882:1998正式发布,标志着C++的正式成熟和广泛应用。 

2. 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" << endl;
    return 0;
}

3. 命名空间

3.1 namespace的价值

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

如下面C语言程序存在的命名冲突问题:

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

运行结果: 

运行时编译报错,编译时头文件<stdlib.h>中有一个rand()函数,是用来产生随机数的一个函数,与我们定义的变量rand名字一样,就产生重定义,为了避免这种情况发生,namespace关键字闪亮登场。

3.2 命名空间的定义 

  • 定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
  • namespace本质是定义出一个域,这个域跟全局域各自独立,不同的域可以定义同名变量,所以下面的rand变量就不存在冲突了。
  • C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找一个变量/函数/ 类型出处(声明或定义)的逻辑,所有有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的生命周期,命名空间域和类域不影响变量生命周期。
  • namespace只能定义在全局,当然还可以嵌套定义。
  • 项目工程中多文件中定义的同名namespace会认为是一个namespace,不会冲突。
  •  C++标准库都放在一个叫std(standard)的命名空间中。
#include<stdio.h>
#include<stdlib.h>
namespace dog    //dog是命名空间的名字,⼀般开发中是⽤项⽬名字作为命名空间名字。
{
	int rand = 10;
}

int m = 5;

int main()
{		
	printf("%p\n", rand);   //stdlib.h头文件rand变量是一个指针,我们用%p打印
	printf("%d\n", dog::rand); //::域作用限定符  指定命名空间中的rand,打印10

	int m = 55;
	printf("%d\n", m); //局部变量优先,打印55
	printf("%d\n", ::m);//访问全局变量m,打印5

	return 0;
}

运行结果:

//命名空间中可以定义变量/函数/类型
#include<stdio.h>
#include<stdlib.h>
namespace dog   
{	
	int rand = 10;

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

	struct Node
	{
		struct Node* next;
		int val;
	};	
}

int main()
{
	printf("%d\n", dog::Add(1, 2)); //3
	struct dog::Node p1;//定义结构体变量

	return 0;
}
//命名空间可以嵌套定义,解决一个命名空间存在同名变量问题
#include<stdio.h>
#include<stdlib.h>

namespace dog
{
	namespace cat
	{
		int rand = 10;
		int Add(int left, int right)
		{
			return left + right;
		}
	}

	namespace tiger
	{
		int rand = 20;
		int Add(int left, int right)
		{
			return (left + right) * 10;
		}
	}
}

int main()
{
	printf("%d\n", dog::cat::rand);  //10
	printf("%d\n", dog::tiger::rand);//20

	return 0;
}

3.3 命名空间的使用

编译查找一个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间里面去查找。所以下面程序会编译报错:

#include<stdio.h>
namespace dog
{
	int a = 0;	
}

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

运行结果:

所以我们要使用命名空间中定义的变量/函数,有三种方式:

  • 指定命名空间访问,项目中推荐这种方式。
  • using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。
  • 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常为了方便练习程序推荐使用。
#include<stdio.h>
namespace dog
{
	int a = 0;	
	int b = 1;
}

int main()
{	
	printf("%d\n", dog::a); //指定命名空间访问
	return 0;
}

#include<stdio.h>
namespace dog
{
	int a = 0;
	int b = 1;
}
using dog::a; //using将命名空间中某个成员展开

int main()
{
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", a);
	printf("%d\n", dog::b);
	return 0;
}
#include<stdio.h>
namespace dog
{
	int a = 0;	
	int b = 1;
}
using namespace dog; //展开命名空间中的全部成员

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

4. C++输入和输出

  • <iostream> 是Input Output Stream的缩写,是标准的输入、输出流库,定义了标准的输入、输出对象。
  • std::cin 是istream类的对象,它主要面向窄字符(narrow characters (of type char))的标准输入流。
  • std::cout 是ostream类的对象,它主要面向窄字符的标准输出流。
  • std::endl 是一个函数,流插入输出时,相当于插一个换行字符加刷新缓冲区。
  • << 是流插入运算符,>> 是流提取运算符。(C语言还用这两个运算符做位运算左移/右移)
  • 使用C++输入输出更方便,不需要像printf/scanf输入输出时那样,需要手动指定格式,C++的输入输出可以自动识别变量类型(本质是通过函数重载实现的),其实最重要的是 C++的流能更好的支持自定义类型对象的输入输出。
  • cout /cin /endl 等都属于C++标准库,C++标准库都放在一个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。 
  • 一般日常练习中我们可以展开命名空间中全部成员 (using namespace std),实际项目开发中不建议。
  • 在vs中没有包含<stdio.h>,也可以使用printf 和scanf,在包含<iostream>间接包含了,vs系列编译器是这样的,其他编译器可能会报错。
#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	double b = 3.1;
	char c = 'y';

	cout << a << " " << b << " " << c << endl;
	std::cout << a << " " << b << " " << c << endl;

	cout << endl;
	//可以自动识别变量的类型
	cin >> a >> b >> c;
	cout << a << " " << b << " " << c << endl;
	return 0;
}

运行结果:

//C++兼容C语言,如果想控制精度打印的话可以直接使用printf
#include<iostream>
using namespace std;

int main()
{
	double b = 22345.666666;
	printf("%.2lf\n", b);

	cout << b << endl;//cout 输出默认精度为 6 位有效数

	return 0;
}

运行结果:

C++ IO的性能

C++为了兼容C语言,会做出一些妥协优化。C语言的缓冲区只有遇到刷新标志时才会进行刷新,而如果printf缓冲区还没有刷新,我们使用cout会出现什么情况?会先把printf缓冲区刷新出来,再打印cout输出的内容,所以cout之前会先对缓冲区进行检查!所以C++风格IO需要和C风格IO进行缓冲区同步!

对于有大量IO的场景,C++的IO效率会比C风格IO慢,可以进行下面优化:

#include<iostream>
using namespace std;
int main()
{
	// 在IO需求⽐较⾼的地⽅,如部分⼤量输⼊的竞赛题中,加上以下3⾏代码,可以提⾼C++ IO效率 

	ios_base::sync_with_stdio(false);
	cin.tie(nullptr);
	cout.tie(nullptr);
	return 0;
}

5. 缺省参数 

  • 缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参,缺省参数分为全缺省半缺省参数。(有些地方把缺省参数也叫默认参数
  • 全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左 依次连续缺省不能间隔跳跃给缺省值。 
  • 带缺省参数的函数调用,C++规定必须左到右依次给实参不能跳跃给实参。 
  • 函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值
  • 缺省值必须是常量,C语言不支持缺省参数(编译器不支持)。
#include <iostream>
using namespace std;

void Func(int a = 0)
{
	cout << a << endl;
}
int main()
{
	Func(); //没有传参时,使⽤参数的默认值

	Func(10); //传参时,使⽤指定的实参
	
	return 0;
}

运行结果:

第一个形参没有给出缺省参数值,至少传入一个实参。

第一、二个形参无缺省参数值,至少传入两个实参。

三个形参均无缺省参数值,需传入三个实参 。

#include <iostream>
using namespace std;

//全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}

//半缺省
void Func2(int a, int b = 10, int c = 20)//从右往左 依次连续缺省,不能间隔跳跃
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl << endl;
}
int main()
{                      //从左到右依次给实参
	Func1();
	Func1(1);
	Func1(1, 2);
	Func1(1, 2, 3);

	Func2(100);
	Func2(100, 200);
	Func2(100, 200, 300);

	return 0;
}

运行结果:

6. 函数重载

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

6.1 实现函数重载的条件 

  • 参数个数不同
  • 参数类型不同
  • 参数顺序不同
#include<iostream>
using namespace std;

// 1、参数类型不同
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}
double Add(double left, double right)
{
	cout << "double Add(double left, double right)" << endl;
	return left + right;
}

// 2、参数个数不同
void f()
{
	cout << "f()" << endl;
}
void f(int a)
{
	cout << "f(int a)" << endl;
}

// 3、参数类型顺序不同
void f(int a, char b)
{
	cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
	cout << "f(char b, int a)" << endl;
}

int main()
{
	Add(1, 2);
	Add(1.1, 2.2);

	f();
	f(5);

	f(8, 'm');
	f('n', 4);

	return 0;
}

运行结果:

#include<iostream>
using namespace std;
//下面两个函数构成重载
//但是调用fg()时会产生歧义,编译器不知道调用谁
void fg()
{
	cout << "fg()" << endl;
}

void fg(int a=10)
{
	cout << "fg(int a)" << endl;
}

int main()
{
	fg();

	return 0;
}

运行结果:

这里无参调用时存在歧义,没办法区分,所以在书写函数时不要一个无参,另一个全缺省。 

#include<iostream>
using namespace std;
//返回值不同不能作为重载条件

void function()
{}

int function()
{
	return 0;
}
int main()
{
	function();
	return 0;
}

运行结果:

返回值不同不能作为重载条件,因为调用时也无法区分。

如果这样调用函数:int x = function (); 通过接受返回值则可以判断出function 是第二个函数,如果我们不接收函数的返回值,在这种情况下,编译器和程序员都不知道哪个function 函数被调用。所以只能靠参数而不能靠返回值类型的不同来区分重载函数。

6.2 函数重载的应用

对于栈这种数据结构,我们定义一个栈的结构体和对它初始化的函数。

//Stack.h
#include<iostream>
#include<assert.h>
using namespace std;

typedef int SLDataType;
typedef struct Stack
{
	SLDataType* a;//指向动态开辟的数组
	int top;//确定栈顶位置
	int capacity;//栈的容量
}ST;

  //初始化
void STInit(ST* pst, int n = 4); //缺省参数不能声明和定义同时给


//Stack.cpp
#include"Stack.h"
void STInit(ST* pst, int n) //函数声明和定义
{
	assert(pst);
	pst->a = (SLDataType*)malloc(sizeof(SLDataType) * n);
	pst->top = 0;
	pst->capacity = n;
}

//Test.cpp
int main()
{
	//确定知道要插⼊1000个数据,初始化时一把开好,避免扩容,效率得到提升
	ST s1;
	STInit(&s1, 1000);
	return 0;
}

如果我们要开确定容量的空间,直接手动传入确定容量,可以避免多次扩容和空间浪费,如果不知道确定要开多少空间,此时可以不传入实参,默认使用给的缺省参数值如果空间不够就继续扩容。


http://www.kler.cn/a/524095.html

相关文章:

  • 【llm对话系统】大模型 RAG 之回答生成:融合检索信息,生成精准答案
  • 如何将xps文件转换为txt文件?xps转为pdf,pdf转为txt,提取pdf表格并转为txt
  • Mac m1,m2,m3芯片使用nvm安装node14报错
  • OpenCV:形态学操作总结
  • 5分钟带你获取deepseek api并搭建简易问答应用
  • 从源码深入理解One-API框架:适配器模式实现LLM接口对接
  • 处理 .gitignore 未忽略文件夹问题
  • 我的2024年终总结和2025年展望
  • DeepseekMath:超强开源数学模型(论文详解)
  • linux开启samba共享文件夹
  • Linux(NFS搭建)
  • 使用Ollama 在Ubuntu运行deepseek大模型:以deepseek-r1为例
  • springboot跨域配置
  • ChatGPT 搜索测试整合记忆功能
  • AndroidCompose Navigation导航精通1-基本页面导航与ViewPager
  • 计算机网络基础 - 链路层(3)
  • 多项日常使用测试,带你了解如何选择AI工具 Deepseek VS ChatGpt VS Claude
  • 【源码+文档+调试讲解】基于springboot的高校实验室预约系统
  • DeepSeek--通向通用人工智能的深度探索者
  • Towards Optimizing with Large Language Model
  • 基于 Android 的校园订餐 APP 设计与实现
  • AUTOSAR从入门到精通-车身控制系统BCM(三)
  • 使用 DeepSpeed 框架训练时如何配置 QLoRA
  • 【力扣每日一题】解答分析 1010. 总持续时间可被 60 整除的歌曲对数
  • MySQL深度解析与优化实践
  • 【问题】Chrome安装不受支持的扩展 解决方案