【C++】入门C++

1.C++的第一个程序

之前写的C语言文件都是后缀为.c的文件,进入C++后就要把后缀改为.c++了,vs编译器看到是.cpp就会调⽤C++编译器编译。C++兼容C语言的绝大多数语法,所以C语言的 hallo word 依旧可以在C++下使用。

//test.cpp
//c语言的hallo world
#include<stdio.h>
int main() {

	printf("%s", "hallo world\n");

	return 0;
}

当然C++也有自己的输入输出,C++的hallo word长这样:

//test.cpp
//c++的hallo world
#include<iostream>//相当于c语言的.h文件
using namespace std;
int main() {

	cout << "hello world\n";

	return 0;
}

2.命名空间

2.1命名空间的价值

在C/C++ 中,标准库中的函数命名与程序员写的函数命名、程序员和程序员之间的(函数,变量,宏等等)这些可能会存在命名冲突,使用命名空间(namespace)可以有效避免命名冲突,使得代码更加模块化和易于管理。
c语⾔项⽬类似下⾯程序这样的命名冲突是普遍存在的问题,C++引⼊namespace就是为了更好的解决这样的问题

//c语言的命名冲突

#include<stdio.h>
#include<stdlib.h> //rand()函数在这个库中,编译链接后展开

int rand = 10;

int main() {
	//C2365 "rand":重定义,以前的定义是"函数"

	printf("%d", rand);

	return 0;
}

2.2命名空间的定义

  • 命名空间的定义需要用到namespac关键字,后面接命名空间的名字,然后在加一对{}即可。{}中即为命名空间的成员。命名空间可以定义变量/函数/类型等。
  • 命名空间的本质是定义了一个新的域,这个域跟全局域互相独立,不同的域可以定义同名变量。这样在命名空间中定义一个rand变量就不会和全局域中的rand函数冲突了。
  • C++中有函数局部域,全局域,命名空间域类域,类域。域影响的是编译器在编译时查找一个变量/函数/类型出处(定义或声明)的逻辑,所以有了域的隔离,名字冲突问题自然就解决了。局部域和全局域除了会影响查找逻辑,还会直接影变量的声明周期,而命名空间域和类域不会直接影响变量的生命周期。
  • 命名空间只能定义在全局,它也可以嵌套定义。
  • 项目工程中的多个文件,会将同名的命名空间当成一个,不会有冲突
  • C++的标准库都放在一个std(standard)的命名空间域中

普通的命名空间域定义

#include<iostream>
#include<stdlib.h>

//普通的命名空间定义 jiuwu是这个命名空间的名字

namespace jiuwu
{
	int a = 10;

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

	struct Node
	{
		struct Node* next;
		int val;
	};
}
int main() {
	
	//这的rand是全局域中的rand函数
	printf("%p\n", rand);

	//这是jiuwu命名空间域中的rand变量
	printf("%d\n",jiuwu::a);

	return 0;
}

嵌套定义命名空间域

#include<iostream>
#include<stdlib.h>

//嵌套命名空间定义

namespace XiangMu_1
{
	namespace jiuwu
	{
		int a = 10;

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

		struct Node
		{
			struct Node* next;
			int val;
		};
	}
	namespace sidangkang
	{
		int a = 20;

		int ADD(float a, float b)
		{
			return a + b;
		}

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

}
int main() {
	
	//这的a是命名空间XiangMu_1中的嵌套jiuwu中的a
	printf("%d\n", XiangMu_1::jiuwu::a);

	//这的a是命名空间XiangMu_1中的嵌套sidangkang中的a
	printf("%d\n", XiangMu_1::sidangkang::a);

	return 0;
}

2.3命名空间的使用

编译器在查找一个变量的声明/定义时,默认只会在局部域和全局域中查找,不会到命名空间域中查找,所以以下程序会报错

include <stdio.h>

namespace jiuwu
{
int a = 0 ;
int b = 1 ;
}
int main ()
{
// 编译报错: error C2065: "a": 未声明的标识符
printf ( "%d\n" , a);
return 0
}

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

  • 指定命名空间访问,项目中推荐这种方式。

  • using将命名空间中某个成员展开,项目中经常访问的不存在冲突的成员推荐这种方式。

  • 展开命名空间中全部成员,项目不推荐,冲突风险很大,日常小练习程序为了方便推荐使用。

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

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

    // using将命名空间中某个成员展开
    using jiuwu::b;
    int main()
    {
    printf("%d\n", N::a);
    printf("%d\n", b);
    return 0;
    }

    //将整个命名空间域展开
    using namespce jiuwu;
    int main()
    {
    printf("%d\n", a);
    printf("%d\n", b);
    return 0;
    }

3.C++的输入&输出

  • <iostream> 是C++的标准输入/输出库,定于了标准输入输出对象

  • std::cin 是istream的对象,它主要面向窄字符的标准输入流

  • std::cout是ostream的的对象,它主要面向窄字符的标准输出流

  • std::endl是一个函数,流插入输出时,相当于一个换行字符加刷新缓冲区

  • <<是流插入运算符,>>是流提取运算符 (c语言还使用它们做为移位运算符)

  • C++的输入输出非常方便,它不用向printf函数那样指定类型,它可以自己识别类型

  • cout/cin/endl都属于C++的标准库,C++的标准库都放在一个叫std(standard)的命名空间中,所以要同命名空间的使用方法去使用它们

  • <iostream>间接包含了<stdio.h>,使用可以直接使用printf和scanf,vs系列的编译器是这样的,其他编译器可能会报错

    #include<iostream>
    int main() {
    int a;
    float b;
    char c;

      //cin = scanf 
      //>>从标准输入流中提取数据
      std::cin >> a >> b >> c; //可自动识别数据类型
    
      //cout = printf
      //<<将数据输入到标准输出流中
      std::cout << a << " "<< b <<" " << c <<std::endl;
    
      
      return 0;
    

    }

4.缺省参数

  • 缺省参数就是在定义或声明函数时为函数的形参指定一个默认值。在调用参数时,如果没有对应的实参那么就会使用设定好的缺省值,否则使用实参。缺省参数右分为全缺省参数与半缺省参数。
  • 全缺省参数就是将函数所有的新参都设计为缺省参数,半缺省参数就是将函数部分参数设置为缺省参数。C++规定半缺省参数只能从右往左连续缺省
  • 带缺省参数的函数的调用,C++规定只能从左往右连续给实参,不能跳跃给实参。
  • 函数声明定义分离时,规定缺省参数必须在声明中给。
cpp 复制代码
#include<iostream>
using namespace std;
//全缺省
void func(int a = 10, int b = 10, int c = 10)
{
	cout << a << endl << b << endl << c;
}

//半缺省
void func1(int a, int b = 10,int c = 10)
{
	cout << a << endl << b << endl << c;
}

//错误半缺省
void func2(int a = 10, int b, int c)//应从右往左连续设置缺省值
{
	cout << a << endl << b << endl << c;
}



int main() {

	func();//在没有实参时使用设计好的缺省值
	func(1);//传参时,使用指定实参

	func1();//不能再没有设置缺省参数的地方,选着不传实参
	func1(1);//这里至少传一个实参
	func1(1, 2);

	return 0;
}

5.函数重载

C++中规定可以在同一作用域中出现同名函数,但是要求它们的形参不同

cpp 复制代码
//函数重载
// 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(int left, int right)" << endl;
	return double + double;
}

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

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

//注意!不能仅通过返回值,与返回类型进行函数重载
int Add(int left, int right)
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

double Add(int left, int right)//返回类型不能作为重载的条件
{
	cout << "int Add(int left, int right)" << endl;
	return left + right;
}

6.引用

6.1什么是引用

引用不是新定义一个变量,而是给现有的变量取一个别名,这个别名并不会新开辟一块内存空间,而是会和被取别名的变量一起使用一块空间。

类型 & 引用别名 = 引用对象

#include<iostream>
using namespace std;
int main() {

	int a = 10;

	//给a这个变量取别名
	int& b = a;
	int& c = a;

	//也可以给别名取别名,但终究还是同一个变量】
	int& d = b;

	//这里我们可以发现它们的内容和地址都是完全一样的
	cout << a << "    " << &a << endl;
	cout << b << "    " << &b << endl;
	cout << c << "    " << &c << endl;
	cout << d << "    " << &d << endl;


	return 0;
}

6.2引用的特性

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

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

3.引用一旦被初始化,就不能再改变其指向的对象

4.由于:引用一旦被初始化,就不能再改变其指向的对象,所以在使用const修饰引用时只能修饰其"内容"

#include<iostream>
using namespace std;
int main() {

	//引用在定义时必须初始化,否则报错
	//int& a;

	//一个变量可有多个引用
	int b = 10;
	int& c = b;
	int& d = b;

	//这⾥不是让f引⽤b,因为C++引⽤不能改变指向,这里是给f赋值即 f = 10;e = 10
	int e = 20;
	int& f = e;
	cout << f<<endl;
	f = b;
	cout << f<<endl;

    //const修饰引用
    const int& ra = a;//正确

    int& const ra = a;//错误 原因:引用本质上是一个已经绑定了某个对象的别名,其"指向"在初始化后是不可变的。因此,为引用添加 const 修饰其"指向"是没有意义的,也是不被编译器允许的。


	return 0;
}

6.3const引用

当引用一个const对象,必须使用const引用const引用 也可以引用普通对象,因为对象的访问权限可以在引用时缩小,但是不能在引用时放大。

当我们引用编译器创建的临时对象时,也需要用const引用。因为临时对象体通常是只读的,对它进行引用而不使用const引用会造成权限的放大

临时对象的产生

1.表达式的结果

某个表达式的结果会被编译器会创建一个临时对象来保存这个结果

//10 + 20 的结果会被存储到临时对象中,然后在赋值给a
int a = 10 + 20;

2.类型转换

当执行显式的类型转换或隐式的类型转换,且转换结果需要被存储时,也可能产生临时对象

//10.2类型转换的结果会被存储在临时对象中,然后在被赋值被a
int b = 10.2;

3.常量引用的绑定

// 编译器会创建一个值为10的临时int对象
const int& a = 10; 

临时对象的生命周期:临时对象的生命周期是自动管理的,并且通常与引用它的作用域相同。一旦引用超出了它的作用域,临时对象就会被销毁。

6.4.指针和引用的区别

  • 在语法层面,引用的创建不需要开空间属于引用对象的别名,而指针的创建需要空间
  • 引用在定义时必须初始化,而指针不用(指针不初始化容易造成野指针问题)
  • 引用在指向一个对象后不能在更改,而指针可以随时更换指向对象
  • 引用可直接访问指向对象,而指针需要解引用
  • sizeof中的含义不同,引用结果为引用对象类型大小,但指针永远是固定的字节(在32位系统上,指针的大小通常是4字节,在64位系统上,指针的大小通常是8字节)

7.内联函数

内联函数只需要在函数前面加上inline关键字,编译器会在有内联函数的地方选择性的展开,这样就不用新开栈帧了,对效率提升有所帮助

内联函数的展开对于编译器来说只是一个建议,对于程序员不合理的展开,编译器会选择视而不见

内联函数的声明和定义不建议分离在不同的源文件中,这样会导致链接错误。因为inline展开就没有地址,编译器就找不到定义的内联函数

在vs编译器debug状态进行反汇编码的观察,我们发现内联函数并未展开,这是为了方便程序员调试,debug状态下展开内联函数需要设置这两个地方

#include<iostream>
using namespace std;

// 正确的宏实现
#define ADD(a, b) ((a) + (b))
// 为什么不能加分号?
// 为什么要加外⾯的括号?
// 为什么要加⾥⾯的括号?

int main() {

	cout << ADD(2, 3) << endl;//如果加了分号就会出现语法错误

	cout << ADD(2, 3) * 4 << endl;//如果不加外面括号->(a)+ (b) * 4,b会先与4乘法运算,跟我们实际需求不符

	int a = 1, b = 2;
	cout << ADD(a | b, b & a) << endl;//如果不加里面的括号->(a | b + b  &a)b 会与 b先进行加法运算

	return 0;
}

8.nullptr

在C++和C中,NULL实际上是一个宏,C++中NULL被替换成 0,C中NULL被替换成(void*)0。但是无论这两种定义,在C++中使用它们的时候都会出一些问题,例如:

void func(int);  
void func(int*);

假如你想调用 void func(int*);你可能会这样做:

func(NULL);

但是由于NULL被替换成了字面量0,所以你实际上调用的是int版本的func函数

你还可能会这样做:

funk((void*)NULL);

这样写在C++中也是错误的,因为在C++中void*指针不能够隐形的转化为其他类型的指针,在C语言中这样写是可以的,因为C语言中的void*指针可以给给任意类型的指针

所以在C++11中引入了nullptr关键字。nullptr实际上是一种特殊的字面量,它能够隐形的转化成任意类型的指针,而不能过转化成整数类型

#include<iostream>
using namespace std;
void func(int a)
{
	cout << "func(int a)" << endl;
}
void func(int* a)
{
	cout << "func(int* a)" << endl;
}
int main() {

	func(NULL);
	//func((void*)NULL);

	func(nullptr);

	return 0;
}
相关推荐
双子座断点1 分钟前
QT 机器视觉 (3. 虚拟相机SDK、测试工具)
qt·1024程序员节
景天科技苑2 分钟前
【Golang】Go语言中如何进行包管理
开发语言·后端·golang·go mod·go语言包管理·go包管理·go sum
wwangxu5 分钟前
Java 面向对象基础
java·开发语言
20岁30年经验的码农14 分钟前
爬虫基础
1024程序员节
wdxylb20 分钟前
Linux下编写第一个bash脚本
开发语言·chrome·bash
幽兰的天空23 分钟前
Python实现的简单时钟
开发语言·python
这题怎么做?!?30 分钟前
模板方法模式
开发语言·c++·算法
licy__33 分钟前
计算机网络IP地址分类,子网掩码,子网划分复习资料
1024程序员节
程序员yt1 小时前
2025秋招八股文--服务器篇
linux·运维·服务器·c++·后端·面试