我是靠谱客的博主 要减肥钢笔,这篇文章主要介绍【C++入门基础篇】---- 万字解析壹 ----  为什么要用命名空间 ?贰 ----  C++输入和输出叁 ---- 缺省参数肆 ----  函数重载伍 ---- 引用六 ---- extern "C" 柒 ---- 内联 八 ---- auto关键字(C++11)玖 ---- 基于范围的for循环(c++11) 十 ----  指针空值nullptr(C++),现在分享给大家,希望可以做个参考。


此篇为C++入门首篇,学习的主要是:命名空间 || C++输入和输出 || 缺省参数 || 函数重载 || 引用 || extern "C" || 内联 || auto关键字(C++11) || 基于范围的for循环(c++11) || 指针空值nullptr(C++)
等内容,此次让小新带领大家一起走向C++第一步


壹 ----  为什么要用命名空间 ?

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

 0x01 :命名冲突域

   ①C++是兼容C的,所以cpp中一样可以使用c的语法

#include<stdio.h>
int rand = 0;
int main()
{
	printf("hello yonghengn");
	printf("%dn,rand");
}

②在stdlib库中有一个产生随机数的函数rand()

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int main()
{
	printf("hello yonghengn");
	printf("%dn",rand);
}

③我们知道 #include包含头文件,头文件会被展开来,此时stdlib库展开,其中包含了一个rand的函数,如果此时再定义一个rand全局变量会是什么结果呢?

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
	printf("hello yonghengn");
	printf("%dn",rand);
}

结果马上揭晓:
没错就是下面这个结果

 
④那么这个问题该如何解决呢?

        此时的良方就是运用命名空间,因为它解决的就是命名冲突问题,namespace中的rand还是全局变量,此时namespace的作用就是为了防止stdlib库中的函数rand与变量rand命名冲突

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
namespace yongheng
{
	int rand = 0;
}
int main()
{
	printf("hello yonghengn");
	printf("%dn",rand);
}

⑤可以通过 作用域限定符进行验证,是否可以显示namespace中变量rand,需要了解的是,机器首先会在局部中去找,然后再到全局中去找,如果不使用作用域限定符的话是不会去namespace中去寻找rand变量的,因为namespace创建的是一个域,相当于将rand变量包裹在这个域中了

0x02: 命名空间的内容
       ①变量 ②函数 ③结构体 等 

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
namespace yongheng
{
	int rand = 0;//变量
	int Add(int left,int right)//函数
	{
		return left + right;
	}
	struct Node //结构体
	{
		struct Node* next;
		int val;
	};

}
int main()
{
	printf("%d",yongheng::rand);
	yongheng::Add(1,2);
	struct yongheng::Node node;
}

0x03: 命名空间可以嵌套命名空间

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<stdlib.h>
namespace N1
{
	namespace N2
	{
		int Add(int left,int right)
		{
			return left + right;
		}
	
	}

}
int main()
{
	printf("%d",N1::N2::Add(1,2)); // 3
}

0x04: 同一个工程允许多个相同名称的命名空间,编译器最后会将其合成同一个命名空间

示例如下:


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

0x05  命名空间的使用

   ① 加命名空间名称及作用域限定符

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

全部展开,用起来方便了,隔离就失效了,所以应该谨慎使用

 ③ 使用using 将命名空间中某个成员引入

//单独展开某一项,用于展开命名空间中常用的
using N1::N2::Node;
int main()
{
	struct Node node;
}

0x06:  三种命名空间的使用示例

   iostream库包含了输入输出流,std是一个标准库命名空间,里面包含了cout(流插入)和cin(流提取)等

 ①将std命名空间展开(适合练习的时候使用)

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;//
int main()
{
	cout << "hello world" << endl;
	return 0;
}

②std命名空间中包含cout与cin和endl等,所以可以指定展开

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
int main()
{
	std::cout << "hello world" << std::endl;
	return 0;
}

③将常用的展开

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using std::cout;
using std::endl;
int main()
{
	cout << "hello world" << endl;
	return 0;
}

贰 ----  C++输入和输出

0x07  cout和cin的运用

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int main()
{
	int i = 0;
	int j = 3.14;
	cout << i << ' ' << j << endl;
	cin >> i >> j;
	cout << i << ' ' <<  j  << endl;
}

   解析如下:

说明:
1. 使用 cout 标准输出 ( 控制台 ) 和 cin 标准输入 ( 键盘 ) 时,必须 包含 < iostream > 头文件 以及 std 标准命名空间。
注意:早期标准库将所有功能在全局域中实现,声明在 .h 后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在 std 命名空间下,为了和 C 头文件区分,也为了正确使用命名空间,规定 C++ 头文 件不带 .h ;旧编译器 (vc 6.0) 中还支持 <iostream.h> 格式,后续编译器已不支持,因此 推荐 使用 <iostream>+std 的方式。
2. 使用 C++ 输入输出更方便,不需增加数据格式控制,比如:整形 --%d ,字符 --%c

0x08: C语言和C++语言的输出比较
示例如下:

上图可以看出,如果只输出数值的话,还是C++更方便一些,C语言保留了6位小数。而如果结构体student中的成员越多的话,还是C语言的输出语句更方便一些,所以C语言和C++哪个更方便就可以选用哪个。

叁 ---- 缺省参数

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

0x09 全缺省参数

示例如下:

 

 全缺省参数: 即上述a,b,c三个都有缺省参数

0x10 半缺省参数

示例如下:


需要注意的是:
1. 半缺省参数必须 从右往左依次 来给出,不能间隔着给
2. 缺省参数不能在函数声明和定义中同时出现
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
个缺省值。
3. 缺省值必须是常量或者全局变量
4. C 语言不支持(编译器不支持)

肆 ----  函数重载

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

0x11  参数类型不同

0x12  参数个数不同

 

0x13  参数顺序不同

0x14  是否构成重载的几种情况

①返回值不同,不能构成重载

②缺省值不同,不能构成重载

 ③构成重载,但是一个存在默认参数,一个不存在默认参数,如下示例,会产生二义性

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

   解析如下:

         首先我们先建立一个头文件 fac.h,再建立俩个文件fac.c和test.c ,然后我们我们回顾一下编译器编译的整个过程:
1. 预处理: 进行的是头文件展开,宏替换,条件编译,去掉注释等等 生成fac.i 和  test.i
2. 编译: 进行检查语法,生成汇编代码  生成fac.s 和  test.s
3. 汇编: 汇编代码转换成二进制机器码  生成fac.o 和 test.o
4. 链接: 生成a.out

① c语言不支持函数重载:因为在编译的时候,俩个重载函数,函数名相同,在fac.o符号表中存在歧义和冲突,其次链接的时候也存在歧义和冲突,因为它们都是直接使用函数名去标识和查找的,而重载函数,它们的函数名又是相同的,所以不支持重载
②c++支持重载函数:因为C++的目标文件符号表中不是直接用函数名来表示和查找的,而是使用函数名修饰规则去查找(在不同编译器下不同),有了这个函数名修饰规则,只要参数不同,fac.o符号表里面重载的函数就不存在二义性和冲突了,所以在链接的时候,test.o的main函数里面去调用这俩个重载的函数,查找他们的地址时,也是非常明确,且没有歧义的

需要注意的是:
①如果当前文件有函数定义,那么在编译的时候就把地址填上了
②如果在当前只有函数的声明,那么定义肯定是在其他的.cpp中,编译时是没有地址的,只能在链接的时候去其他的.o符号表中根据函数名修饰规则去找,这是链接部分所做的
③函数名修饰规则: _z + 函数名长度 + 函数名 + 参数首字母 如 函数名 f() :   _z1fv

伍 ---- 引用

引用 不是新定义一个变量,而 是 给已存在变量取了一个别名 , 编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
比如:
李逵 ,在家称为 " 铁牛 " ,江湖上人称 " 黑旋风。

  实例如下:

 类型& 引用变量名(对象名) = 引用实体

引用在语法层上来讲是没有开辟新的空间的,就是对原来的空间取了一个新名称叫做b
注意:引用类型必须和引用实体是同种类型的

0x16 引用特性

   ① 引用在定义时必须初始化及一个变量可以有多个引用

  ②同一个域中不能有相同的名字

 d是a的小名,那就不能有一个人的大名叫d,即同一个域中不能有相同的名字

 ③引用一旦引用一个实体,再不能引用其他实体

④引用与指针的对比

指针可以看作是一个渣男,第一次喜欢A,第二次又喜欢B. 而引用一旦喜欢上A,他就不可能喜欢B了

0x17 传指针、传引用、传值

//传指针
void Swap(int* p1,int* p2)
{
	int temp = *p1;
	*p1 = *p2;
	*p2 = temp;
}
//传引用,引用做参数,把a,b起个别名
void Swap(int& r1,int& r2)
{
	int temp = r1;
	r1 = r2;
	r2 = temp;
}
//传值
void Swap(int r1, int r2)
{
	int temp = r1;
	r1 = r2;
	r2 = temp;
}
//他们三个构成重载(类型不同)
//但是Swap(a,b);调用是存在歧义,他们不知道调用,传值还是传引用

int main()
{
	int a = 10, b = 20;
	Swap(&a,&b);
	/*Swap(a,b);*/
}

 0x18 传引用作参数的应用

 0x19 传引用作返回值

注意: 如果函数返回时,出了函数作用域,如果返回对象还未还给系统(全局变量、静态区),则可以使用引用返回,如果已经还给系统了,则必须使用传值返回,不能使用传引用返回.

0x20 常引用

//常引用
int main()
{
	//权限放大: a是只读的,而b是a的引用,竟然是可读可写的,那是不可以的
	const int a = 10;
	int& b = a;

	//权限不变: c和d都是只读的 ,可以
	const int c = 10;
	const int& d = c;

	//权限缩小:f是只读的,可以
	int e = 10;
	const int& f = e;
}

0x21  const在引用中的应用

 上图解析:

第一个图解析:d是double类型,产生临时变量,通过隐式类型转换,变为Int类型,第二个是d相当于是不可修改+int 类型,所以要加const

第二个图解析:x1 + x2 这个结果会产生一个临时变量,临时变量是具有常属性的,不可修改,所以 所以这个临时变量可以看作是一个int类型的数据+不可修改这个特性,所以ret 前面要加const

0x22 引用和指针的不同点

1. 引用 在定义时 必须初始化 ,指针没有要求
2. 引用 在初始化时引用一个实体后,就 不能再引用其他实体 ,而指针可以在任何时候指向任何一个同类型实体
3. 没有 NULL 引用 ,但有 NULL 指针
4. 在 sizeof 中含义不同 : 引用 结果为 引用类型的大小 ,但 指针 始终是 地址空间所占字节个数 (32 位平台下占4 个字节 )
5. 引用自加即引用的实体增加 1 ,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同, 指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全
9.引用概念上定义一个变量的别名(没开辟空间),指针存储一个变量的地址
10.底层汇编实现时:引用是用指针实现的

六 ---- extern "C"

有时候在 C++ 工程中可能需要 将某些函数按照 C 的风格来编译 , 在函数前加 extern "C",意思是告诉编译器, 将该函数按照 C 语言规则来编译。

0x23 c++项目调用C静态库

1.包含头文件
2.在工程属性中配置静态库的目录和添加静态库
注意:可以理解为,只有c++认识extern C

 步骤如下:

0x24 C工程调用C++库步骤

解析:只有C++认识extern "C",静态库为C++文件时,告诉机器按C的修饰规则去找,而C项目中,头文件展开,直接就是函数名

总结:
C++程序调用C的库,在C++程序中加extern"C"
C程序调用C++的库,在C++库中加extern"C"

 柒 ---- 内联

因为调用函数,需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,频繁的调用函数,这样的操作是有消耗的。而C和C++都有应对的措施。

0x25   C语言通过宏(一种替换,不需要建立栈帧)

#define  Add(x,y) ((x)+(y))
//最好把((x)+(y))中x和y括号都加上,否则比如(x+y),10*Add(3,4),会替换为10 * 3+4,会导致出错

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

0x26  inline 内联函数

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

查看方式:

1. 在 release 模式下,查看编译器生成的汇编代码中是否存在 call Add
2. 在 debug 模式下,需要对编译器进行设置,否则不会展开 ( 因为 debug 模式下,编译器默认不会对代码进行优化 )

0x27  inline特性

1. inline 是一种 以空间换时间 的做法,省去调用函数额开销。 所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议 ,编译器会自动优化,如果定义为 inline 的函数体内有循环 / 递归等 等,编译器优化时会忽略掉内联。
3. inline 不建议声明和定义分离(直接再定义前面加inline) ,分离会导致链接错误。因为 inline 被展开,就没有函数地址了,因为内联函数都在调用的地方展开了,链接就会找不到。

 八 ---- auto关键字(C++11)

在早期 C/C++ 中 auto 的含义是:使用 auto 修饰的变量,是具有自动存储器的局部变量 ,但遗憾的是一直没有人去使用它,大家可思考下为什么?
C++11 中,标准委员会赋予了 auto 全新的含义即: auto 不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器, auto 声明的变量必须由编译器在编译时期推导而得 。

实例如下:

 

 0x28  实例

int main()
{
	const int a = 0;
	int b = 0;
	//自动将a的类型推到c,  
	auto f = a;//int
	auto c = &a;//int const*
	auto d = 'A';//char
	auto e = 10.11;//double
	//f,d,e的类型是const的,具有常性,但是auto会忽略这个const

	//typeid打印变量的类型
	cout << typeid(f).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	cout << typeid(e).name() << endl;

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

0x29  auto的使用

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

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

        ②   在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量 。
void TestAuto()
{
 auto a = 1, b = 2; 
 auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}

0x30  auto 不能推导的场景

        ① auto不能作为函数的参数

        ② auto  不能直接用来声明数组

玖 ---- 基于范围的for循环(c++11)

0x31 范围for的语法

在C/C++中遍历数组可以有如下的方式:

    //语法糖
	int array[] = {1,2,3,4,5};
	//c/c++遍历数组
	for (int i = 0; i < sizeof(array)/sizeof(int);i++)
	{
		cout << array[i] << endl;
	}
对于一个 有范围的集合 而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此 C++11中引入了基于范围的for循环 。 for 循环后的括号由冒号 “ : ” 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围 。
    //C++11 范围for
	//自动依次取数组array中的每个元素赋值给e
	for (auto e:array)
	{
		cout << e << endl;
	}
注意:与普通循环类似,可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环 。

 那如何运用范围for将数组中的每个值+1呢?

    for (auto e : array)
	{
		e++;//此时是array数组将每个值赋值给e,e的改变并不会影响到array数组中的值
	}

这个方法是不行的,因为此时是array数组将每个值赋值给e,e的改变并不会影响到array数组中的值,那怎样才能改变呢?可以用引用的方法,数组中的每个值赋值给e变量,而e变量又是该值的别名,修改的是同一份空间,所以会改变数组的值,而应该注意的是每次的e的赋值的作用域只在一个循环之中。这种方法是可以解决这个问题的,代码如下:

	for (auto& e : array)
	{
		e++;
	}

0x32 范围for 的使用条件

for 循环迭代的范围必须是确定的 :
对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin 和 end 的方法, begin 和 end 就是 for 循环迭代的范围。
void TestFor(int array[])
{
	for (auto& e : array)
		cout << e << endl;
}

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

 以上的代码是有问题的,for的范围是不确定的,因为范围for的范围必须是数组名,而数组传参,传过去之后会退化为指针,所以其实TestFor的array是一个指针,所以是有问题的,应当注意

 十 ----  指针空值nullptr(C++)

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

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

此时NULL可能被定义为常量,或者被定义为无类型指针(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(0)对应的都是f(int),而程序本意是想通过f(NULL)调用指针版本的f(int*)函数,但是由于NULL被定义成0,因此与程序的初衷相悖。而在C++98中,字面常量0既可以是一个整形数字,也可以是无类型的指针(void*)常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0, 所以在C++11中,引入了新关键字nullptr。

注意:

1. 在使用 nullptr 表示指针空值时,不需要包含头文件,因为 nullptr 是 C++11 作为新关键字引入的 。
2. 在 C++11 中, sizeof(nullptr) 与 sizeof((void*)0) 所占的字节数相同。
3. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用 nullptr 。

最后

以上就是要减肥钢笔最近收集整理的关于【C++入门基础篇】---- 万字解析壹 ----  为什么要用命名空间 ?贰 ----  C++输入和输出叁 ---- 缺省参数肆 ----  函数重载伍 ---- 引用六 ---- extern "C" 柒 ---- 内联 八 ---- auto关键字(C++11)玖 ---- 基于范围的for循环(c++11) 十 ----  指针空值nullptr(C++)的全部内容,更多相关【C++入门基础篇】----内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(255)

评论列表共有 0 条评论

立即
投稿
返回
顶部