C++入门

目录

1、命名空间

命名空间的使用

2、C++输入输出

3、缺省参数

4、函数重载

函数名修饰规则

extern"C"

5、引用

引用概念

常引用

 引用的使用场景

引用和指针的区别

6、内联函数

概念:

 特性

7、auto关键字

auto使用细节

8、范围for循环

语法

使用条件

9. 指针空值nullptr


1、命名空间

一个大型项目往往都是由多名程序员共同完成的,因此会用到大量的变量和函数以及C++里面的类,所以难免会出现命名相同的情况。就算当前程序在你的机器上运行是正常的,当合并时就会出现命名冲突。

举个例子:

namespace A{
    int a = 10;//A定义的变量a
}
namespace B{
    int a = 6;//B定义的变量a
}

如上如果合并的时候,a变量会被重定义,所以C++引入了命名空间(namespace)的概念。

语法:namespace  name{        //name任意名字都可以,只要不跟其他命名空间名冲突

        //变量、函数、类

}

命名空间的使用

Ⅰ 使用命名空间名加作用域限定符,来指明要使用的命名空间

namespace A //A是命名空间名
{
	int a = 1;
	int b = 2;
	int Add(int a, int b)
	{
		return a + b;
	}
}

int main()
{
	printf("%d\n", A::a);  //1
	printf("%d\n", A::b);  //2
	int sum = A::Add(A::a, A::b);
	printf("%d\n", sum);   //3

	return 0;
}

Ⅱ 使用using关键字声明要使用的命名空间成员

using A::a;//引入A空间里的变量a
using A::b;
using A::Add;//引入A空间里的函数Add
int main()
{
	printf("%d\n", a);    //1
	printf("%d\n", b);    //2
	int sum = Add(a, b);
	printf("%d\n", sum);  //3

	return 0;
}

Ⅲ 使用using namespace 命名空间名称引入

using namespace A;
int main()
{
	printf("%d\n", a);    //1
	printf("%d\n", b);    //2
	int sum = Add(a, b);
	printf("%d\n", sum);  //3
	system("pause");
	return 0;
}

注意:当main函数内有重名的变量时,需要特别声明该变量属于哪里

using namespace A;

int main()
{
	int a = 6;
	printf("%d\n", A::a);//1
    printf("%d\n", a);   //6
	printf("%d\n", b);   //2
	int sum1 = Add(A::a, b);//必须指明a属于那里,否则采取就近原则使用main函数里的a
    int sum2 = Add(a, b);//使用的是main函数里的a
	printf("%d\n", sum1);//3
    printf("%d\n", sum2);//8
	system("pause");
	return 0;
}

当有同名的Add函数时,同样需要指明使用的是哪里的Add函数

namespace A 
{
	int a = 1;
	int b = 2;
	int Add(int a, int b)
	{
		return a + b;
	}
}
using namespace A;

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

int main()
{
	int a = 6;
	printf("%d\n", A::a); //1
	printf("%d\n", b);    //2
	int sum1 = ::Add(A::a, b);//使用的是全局域的Add
    int sum2 = A::Add(A::a, b);//使用的是命名空间A里的Add函数
	printf("%d\n", sum1); //30
    printf("%d\n", sum2); //3

	return 0;
}

2、C++输入输出

在C++中输入输出使用的是cout和cin,相对于C语言的printf和scanf使用起来方便一些。

具体使用示例:

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

1、使用使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空间。

2、使用cin和cout输入输出数据时不用添加数据格式控制,比如:整形--%d,字符--%c,因为它会自动推导。

#include<iostream>
using namespace std;

int main()
{
	int a = 1;
	double b = 1.1;
	char c;
	cin >> c;
	cout << a << endl;	//1
	cout << b << endl;	//1.1
	cout << c << endl;	//你输入的字符

	return 0;
}

3、缺省参数

缺省参数是声明或者定义函数时给函数的参数指定一个默认值。这样当你调用该函数时可以传值,也可以不传直接使用默认值。

具体用法:

#include<iostream>
using namespace std;

//int Add(int x = 10, int y = 20, int z = 30)    //全缺省
int Add(int x, int y = 20, int z = 30)    //半缺省
{
	return x + y + z;
}

int main()
{
	int a = 1;
	int b = 2;
	int c = 3;
	cout << Add(a, b, c) << endl;//6    //指定值
    cout << Add() << endl;    //全缺省可以不传值
    //cout << Add() << endl;//当你是半缺省时,没给初始值的得传值,所以这种半缺省时会报错
    cout << Add(10) << endl;//60    //半缺省,有几个没给初始值就传几个

	return 0;
}

特别声明:

1、如果 x 给了缺省值,那么它右边的参数就必须给缺省值,半缺省参数必须从右往左依次来给默认值,不能间隔着给。

2、当你传了值时,优先使用传过来的值。

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

// .h
void Func(int a = 10);
// .cpp
void Func(int a = 20)
{}

//上面的写法是错误的,编译器无法确定使用哪个缺省值
//下面是正确写法
// .h
void Func(int a = 10);
// .cpp
void Func(int a)
{}

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

5、C语言不支持缺省参数

4、函数重载

概念:函数重载就相当于一词多义,在C++同一作用域中,允许声明多个同名函数,他们执行的功能类似,这些同名函数的形参列表(参数个数或类型或顺序)必须不同

具体实例:

#include<iostream>
using namespace std;

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

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

int Add(int x)
{
	return x;
}

int main()
{
	int a = 1;
	int b = 2;
	double c = 1.1;
	cout << Add(a, b) << endl;//3
	cout << Add(a, c) << endl;//2.1
	cout << Add(a) << endl;	  //1

	system("pause");
	return 0;
}

注意:重载函数与函数返回值没有任何关系

函数名修饰规则

为什么C语言不支持函数重载而C++支持呢?

在C/C++中 ,程序要经历预处理、编译、汇编、链接才能生成可执行程序。

预处理阶段:展开头文件、替换宏、删除注释..,生成 .i 文件

编译:进行词法分析、语法分析、语义分析、代码优化,将代码转换成汇编代码,生成 .s 文件

汇编:将汇编代码转换成机器能读懂的二进制代码,生成 .o 文件

链接:将生成的多个 .o 文件链接起来,最后生成 .exe 可执行程序

1、假设这里所有Add函数声明和定义在 A.cpp 文件中,main函数在 test.cpp 中,在编译后链接前,当需要调用Add函数时,由于 test.o 中没有Add函数的定义,就需要去 A.o 中去找。

2、所以链接阶段就需要处理这个问题,当需要调用test.o调用Add函数,就需要知道Add函数的地址,所以就要到A.o文件中去找Add函数,然后链接起来。

3、这里就引入了函数名修饰规则的概念,由于windows下vs修饰规则过于复杂,所以在linux下实验。

上面gcc编译后, 函数名字没有发生任何改变。

可以看到每个Add函数的名字都不同。

windows下的函数名修饰规则可以自行百度了解一下。

结论:C语言同名函数无法区分所以不支持函数重载,C++通过函数修饰规则区分同名函数,最后修饰的函数名不同,所以支持函数重载。

extern"C"

在函数前加extern "C",意思是告诉编译器, 将该函数按照C语言规则来编译。

使用方法:

extern "C" int Add(int a, int b)
{
    return a + b;
}

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

因为C++支持函数重载,在编译器编译函数时会带上函数名加参数类型,如果加上extern "C",那么编译函数时就不会带上参数类型,只会用函数名。

总而言之:extern "C" 的作用就是实现C和C++之间代码的相互调用。

5、引用

引用概念

引用不同于指针,不是给重新定义一个变量,而是给已经存在的变量取一个别名,就相当于你家人给你取的小名或者同学给你取得绰号,所以说一个变量的引用可以有多个引用的变量共用同一块内存空间

举例:

int main()
{
	int a = 10;
	int& b = a;
	int& c = a;//b、c就是a的引用
	cout << b << " " << c << endl;//10、10
	int& d = b;//d是b的引用,本质上代表a
	d = 20;
	cout << a << " " << d << endl;//20、20
    cout << "a地址:" << &a << endl;
	cout << "b地址:" << &b << endl;
    cout << "c地址:" << &c << endl;
	cout << "d地址:" << &d << endl;

    return 0;
}

 可以看到a、b、c、d共享同一块空间。

注意:

1、引用类型必须与引用实体的类型一致

2、引用在定义时必须初始化

3、引用初始化后不可更改引用的实体

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

常引用

int main()
{
	const int a = 10;
	//a是const int类型,是常量,值不可修改
	//而b是int类型,可修改值,属于权限的放大
	//int& b = a;//err
	const int& b = a;//这种才是正确的

	int x = 10;
	const int& y = x;//权限缩小
	int& z = x;
	z = 20;
	//y = 20;//y是const类型,不可修改值,err

    return 0;
}

 引用的使用场景

1、引用做参数

void print(int& s)
{
	s += 10;
	cout << s << endl;//20
}

int main()
{
	int a = 10;
	print(a);
	cout << a << endl;//20
    
    return 0;
}

如上,当int& s改为int s时,我们知道形参是实参的一份临时拷贝,形参的改变不会改变实参,但是当你是引用传参时,s 就是 a 的引用,相当于就是 a 本身,所以 s 的改变也会影响 a,当 a 的空间占用很大时,引用传参的价值就体现的很明显,因为引用传参不会生成额外空间。

2、引用做返回值

int s;
int& print()
{
	s += 10;
	return s;
}

int main()
{
	cout << print() << endl;//10

    return 0;
}

当返回的变量出了函数作用域还没有销毁时,就可以使用引用传参做返回值;如果出了作用域变量销毁就只能传值返回,此时就会发生拷贝,将 s 的值拷贝给临时变量,再将临时变量的值拷贝给调用方。

结论:引用传参和引用做返回值能提高效率。

引用和指针的区别

语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。

上面的实验也看到引用和实体共享同一块空间。

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

下面通过一段代码来对比一下指针和引用:

int main()
{
	int a = 10;
	
	int& b = a;
	b = 15;

	int* p = &a;
	*p = 15;

	return 0;
}

可以看到引用和指针的汇编代码是一模一样的。

那么引用和指针有什么不同呢?

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

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

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

4. 引用大小为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占 4个字节)

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

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

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

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

6、内联函数

概念:

inline是C++关键字,在函数声明或定义中,函数返回类型前加上关键字inline,即可以把函数指定为内联函数。这样可以解决一些频繁调用的函数大量消耗栈空间(栈内存)的问题。关键字inline必须与函数定义放在一起才能使函数成为内联函数,仅仅将inline放在函数声明前面不起任何作用。inline是一种“用于实现”的关键字,而不是一种“用于声明”的关键字

 可以看到添加inline关键字后,main函数不会进入Add函数,而是变成两条汇编指令,我们知道调用函数需要建立栈帧,加了inline关键字后,就节省创建销毁栈帧的开销,提升效率。

如果需要查看inline运行效果,这里以vs2022为例:

 特性

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

2、inline对于编译器来说只是一种建议,具体会不会定义成内联函数,取决于编译器,小函数编译器在release中一般都会自动优化为内联函数。

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

7、auto关键字

在C++11之前,auto关键字用来指明变量的存储类型,auto表示变量是自动存储的,所以在我们编写代码时一般不写,因为自动存储是编译器默认就有的规则。因此auto就没有了存在感。

在C++11之后,auto的可以说非常好用。此后的auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

使用方法:

int main()
{
	int a = 10;
	auto b = a;
	auto c = 'a';

	cout << typeid(b).name() << endl;//int
	cout << typeid(c).name() << endl;//char

	return 0;
}

auto自动推导出了 b 和 c 的类型。

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

auto使用细节

1、auto和指针引用一起使用

int main()
{
	int x = 10;

	//a 和 b 指向的都是x的地址空间
	//a 推导的是int*,b 推导的是int
	auto a = &x;
	auto* b = &x;

	//要让 c 成为 x 的引用,必须加 & 符号
	auto& c = x;
	auto d = x;//这种只是单纯的将 x 的值赋给 d,两个地址不同
	cout << typeid(a).name() << endl;//int*
	cout << typeid(b).name() << endl;//int*
	cout << typeid(c).name() << endl;//int
	cout << typeid(d).name() << endl;//int

	cout << "x的地址:" << &x << endl;
	cout << "c的地址:" << &c << endl;
	cout << "d的地址:" << &d << endl;

	*a = 20;
	*b = 30;
	c = 40;

	return 0;
}

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

int main()
{
	auto a = 1, b = 2;

	//x、y类型不同,auto一次推到多个值时,这些值类型必须相同
	//auto x = 10, y = 10.1;//err

	return 0;
}

3、auto不能作为函数参数

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

4、auto不能直接用来声明数组

int main()
{
   int a[] = {1,2,3};
   //auto b[] = {4,5,6};//err
    
   return 0;
}

8、范围for循环

C++11新增语法

语法

int main()
{
	int arr[5] = { 1, 2, 3, 4, 5 };

    //普通for循环打印
	for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
    
    //范围for循环打印
    //利用auto自动推导arr类型,也可以指定类型
    //e 只是一个变量名,随便取
	//for (auto e : arr)
    for (int e : arr)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

范围for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围。

 范围for与不同循环类似,可以使用continue来结束本次循环,也可以用break来跳出整个循环。

使用条件

1、范围for迭代范围必须是确定的

2、迭代的对象需要实现++和--操作。(迭代器得学习STL了解)

9. 指针空值nullptr

在C语言中NULL代表的是空值,而在C++11中引入了nullptr表示空值

现在的NULL在C++中被定义成一个宏,在传统的C头文件(stddef.h)中,可以看到如下代码:

 可以看到,NULL被定义为字面常量0,或者被定义为无类型指针(void*)的常量

所以在C++中表示空值要使用nullptr,如果继续使用NULL可能会引发一些错误。

void A(int)
{
	cout << "A(int)" << endl;
}
void A(int*)
{
	cout << "A(int*)" << endl;
}
int main()
{
	A(0);
	A(NULL);
	A((int*)NULL);
    A(nullptr);

	return 0;
}

如果按以前的思维,A(NULL)应该调用的应该是int*,但由于现在NULL被定义成宏表示0,所以违背本意,如果还想用NULL调用int*就需要进行强转,或者直接使用nullptr。

注意:

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

2. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。(32位4字节,64位8字节)

3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值