C++基础入门

目录

1、命名空间

1.1 命名空间的使用

1.2 CPP优势简单介绍

1.3 auto关键字

1.5 for循环简化

2、缺省参数

2.1 全缺省

2.2 半缺省

3、函数重载

4、引用

4.1 使用注意点权限问题

4.2 做参数、做返回值

4.3 引用与指针的区别

5、 C和C++的相互调用

5.1 C++程序调用C库

5.2 C程序调用C++库

6、内联函数

7、空指针


1、命名空间

C/C++ 中,变量、函数和后面类的学习中都是大量存在的,这些变量、函数和类的名称将都存在于全局作用域中,可能会导致很多冲突。
使用命名空间的目的是对标识符的名称进行本地化,以 避免命名冲突或名字 污染 namespace 关键字的出现就是针对这种问题的。
简单来说就是在命名空间中可以重复使用与其它作用域中相同变量名字,并且不会发生冲突。
namespace TT1
{
 // 命名空间中的内容,既可以定义变量,也可以定义函数
 int a;
 int Add(int X, int Y)
 {
 return X * Y;
 }
}
//命名空间可以嵌套
namespace TT2
{
 int a;
 int b;
 int Add(int X, int Y)
 {
 return X * Y;
 }
 
 namespace TT3
 {
 int c;
 int d;
 int Sub(int X, int Y)
 {
 return X - Y;
 }
 }
}
//同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中。
namespace TT1
{
 int Mul(int X, int Y)
 {
 return X * Y;
 }
}

命名空间就是开辟了一个新的作用域

1.1 命名空间的使用

命名空间的使用有三种

namespace CC
{
  int a = 10;
  int b = 20;
  int Sum(int x, int y)
 {
    return x + y;
 }

}

int main()
{

  printf("%d",CC::a);
  return 0;
}
namespace CC
{
  int a = 10;
  int b = 20;
  int Sum(int x, int y)
 {
    return x + y;
 }

}

using namespace CC;//把CC命名空间暴露出来

int main()
{

  printf("%d",a);
  Sum(2,3);
  return 0;
}
namespace CC
{
  int a = 10;
  int b = 20;
  int Sum(int x, int y)
 {
    return x + y;
 }

}

using CC::a;//只把a暴露出来

int main()
{

  printf("%d",a);
  return 0;
}

1.2 CPP优势简单介绍

我们在使用C输出输出时,例如使用printf打印整型变量,浮点型变量等,需要指定类型%d等进行输出,输入定义变量时也需要对类型进行定义,但是在C语言中我们就可省去这些麻烦的操作,CPP中会自动识别类型,下面简单的带大家来看一下。

#include<iostream>//CPP的头文件,和C中stdio.h作用类似
using namespace std;//把C++标准库里面的内容暴露出来

int main()
{

 cout<<"Hello world!!!"<<endl;
 printf("Hello world!!!\n");//作用一样

 return 0; 

}
1. 使用 cout 标准输出对象 ( 控制台 ) cin 标准输入对象 ( 键盘 ) 时,必须 包含 < iostream > 头文件 以及按命名空间使用方法使用std
2. cout cin 是全局的流对象, endl 是特殊的 C++ 符号,表示换行输出,他们都包含在包 <iostream > 头文件中。
3. << 是流插入运算符, >> 是流提取运算符
4. 使用 C++ 输入输出更方便,不需要像 printf/scanf 输入输出时那样,需要手动控制格式。 C++ 的输入输出可以自动识别变量类型。
#include <iostream>
using namespace std;
 
int main()
{
 int a;
 double b;
 char c;
 
 // 可以自动识别变量的类型
 cin>>a;
 cin>>b>>c;
 
 cout<<a<<endl;
 cout<<b<<" "<<c<<endl;
 return 0;
}

1.3 auto关键字

看了上面CPP优势的简单介绍,我们会想有没有在定义变量时就能自动识别类型的好东西,auto就可以满足需求
int main()
{
 int a = 77;
 auto b = a;
 auto c = 'a';
 auto d = 10.1;
 
 //auto d; 无法通过编译,使用auto定义变量时必须对其进行初始化
 return 0; 
}

这里的b就是int类型的变量,大家可以使用typeid(b).name()打印出来看一看

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

 auto a = &x;

 auto* b = &x;//auto和auto*没有区别

 auto& c = x;//取别名时有区别要加&

 auto d = 2, e = 3;//可同时定义多个变量

 auto f = 4, g = 4.1;//错误,因为f,g初始化表达式类型不同

 cout << typeid(a).name() << endl;
 cout << typeid(b).name() << endl;
 cout << typeid(c).name() << endl;

return 0;
}

auto不能作为函数参数时使用,编译器无法推导a的实际类型

int Test(auto a)//错误
{}

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

void Test()
{
    auto b[] = {7, 8, 9};//错误
}

1.5 for循环简化

void TestFor()
{
 int array[] = { 1, 2, 3, 4, 5 };
 for(auto& e : array)
 e *= 2;
 
 for(auto e : array)
 cout << e << " ";
 
 return 0; 
}
for 循环后的括号由冒号 分为两部分:第一部分是范围内用于迭代的变量, 第二部分则表示被迭代的范围
可以用 continue 来结束本次循环,也可以用 break 来跳出整个循环
注意事项
1. for 循环迭代的范围必须是确定的 对于数组而言,就是数组中第一个元素和最后一个元素的范围 ;对于类而言,应该提供 begin end 的方法,begin end 就是 for 循环迭代的范围。
注意:以下代码就有问题,因为 for 的范围不确定
void Test(int arr[])
{

   for(auto& e : arr)
   cout<< e <<endl; 

}

2、缺省参数

缺省参数是声明或定义函数时 为函数的 参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。(老工具人了)
大家可以用下面的例子自行试验一下。
void Wwe(int a = 3)
{
  count<<a<<endl;
 
}

int main()
{
 Wwe();
 Wwe(1);
}

2.1 全缺省

简单来说就是有多个参数,全部都缺省
void TestFunc(int a = 10, int b = 20, int c = 30) {
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl; 
}

2.2 半缺省

只给一部分参数缺省
void TestFunc(int a, int b = 10, int c = 20) {
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl; 
}

那么需要注意的问题来了

void TestFunc(int a =10, int b = 10, int c) {
 cout<<"a = "<<a<<endl;
 cout<<"b = "<<b<<endl;
 cout<<"c = "<<c<<endl; 
}

像上面这样写行不行,答案是不行的,半缺省参数必须从右往左依次给出,不能间隔给。

那么我们在使用函数时,如果不小心在头文件和源文件中都进行了传参,那么变量的值将会是谁的?

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



//test.h
void TestFunc(int a = 10);

// test.c
void TestFunc(int a = 20)
{}

答案是编译器会报错,因为声明和定义位置同时出现时,两个位置提供的值还不同,编译器就无法确定用哪个缺省值。

我们也可以给上不同的值试试,声明的优先级是高于定义的,所以我们一般只在声明处给缺省参数。

3、函数重载

这又是相比较C语言一个好的优点
我们在C中定义函数时,函数的名字并不可以重复,但是在这里 函数名字可重复,使用方便,不用愁起名字。
但是我们需要注意 函数重载 : 是函数的一种特殊情况, C++ 允许在 同一作用域中 声明几个功能类似 的同名函数 ,这些同名函数的 形参列表 ( 参数 个数 或 类型 或 顺序 ) 必须不同 ,常用来处理实现功能类似数据类型不同的问题
int multi(int x, int y)
{
 return x + y;
}

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

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

那么为什么C++支持函数重载,C不支持

我们知道C/C++中,程序的运行,需要:预处理、编译、汇编、链接这一个过程

我们以Test.h Test.c main.c三个文件为例进行讲解

 现在我们知道了去符号表中去找函数的地址,其中函数重载的关键就在其中

在符号表中C++有函数名修饰规则,而C是没有的,当C中的函数名相同,就会发生冲突,不知道找谁,而在C++中我们在下面举个例子简单解释一下,在符号表中

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

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

-z5multidi     -z5multiid   数字是函数名字长度,后面两个是函数参数类型,而在C中没有这些东西

4、引用

我们可以先理解为给变量起个外号。你可能以为和typedef一样,但是大大滴不同

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。它和指针作用类似6不6
void Test()
{
 int a = 10;
 int& aa = a;//起别名
 
 printf("%p\n", &a);
 printf("%p\n", &aa);
}

引用时注意保持类型一致

1. 引用在 定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体

4.1 使用注意点权限问题

首先我们要先记住权限只能缩小平移,不可放大,权限问题只针对指针,引用

int main()
{
int a=10;
int&b=a;
cout<<typeid(a).name()<<endl;//typeid()打印类型
const int c =20;
//权限放大报错(权限不可放大)因为d可以修改,而c是不能修改的
int& d =c;

//权限平移
const int& d=c;
int e=30;

//权限缩小
const int& f=e;

int i =1;
double dd =i;
double& ddd=i;//错误

const double& ddd = i;//正确
const int&x = 10;
return 0;
}

上面的double dd = i;我们对dd修改并不会对i产生影响,因为这里的i赋值,是创建了一个临时变量,并且这个临时变量是具有常属性的const,我们使用double& dd=i;就相当于放大了权限,就会发生错误,而const double& ddd=i;就是正确的

4.2 做参数、做返回值

做参数

//使用引用做参数,可以改变实参,大对象传参效率高,因为不需要再拷贝一份
void Swap(int& x,int& y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

//对实参无影响
void Swap(int x,int y)
{
    int tmp = x;
    x = y;
    y = tmp;
}

如果使用传引用传参,函数内如果不改变参数,尽量使用const传参

做返回值

int& Num()
{
    int n = 0;
    n++;
    return n;
}

这里的return n和上面讲的原理一样,返回的是n的拷贝参数,出了这个函数 n就被销毁了,也无法对n做出改变,典型的错误传引用返回,下面才是正确的使用方式

int& Num()
{
    static int n = 0;//n在堆区,出了函数并不会被销毁
    n++;
    return n;
}

Num()++;//n++

接收时使用int& m = Num();

4.3 引用与指针的区别

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

5、 C和C++的相互调用

C++是兼容C的,因此可以相互调用,不过中间的过程需要一些调整

5.1 C++程序调用C库

我们以静态库的形式进行讲解

首先创建一个.c的项目文件,右击项目点击属性,将配置类型改为静态库,完成后F5运行

 然后我们创建一个新的项目,创建.cpp文件,我们需要包含C项目的头文件

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//#include"Stack.h"//默认在当前目录下找,没有可以用相对路径或者绝对路径或者复制过来

//#include"../StackC++_C 7-9/StackC++_C 7-9/Stack.h"// ..表示上一层

大家可以自己操作一下,发现还是使用不了自己写的C库,观察编译器报错原因为链接错误link,这时我们需要右击.cpp项目,属性,常规,将附加库目录写入你的库文件所在路径

然后点击输入,将附加依赖项改为你的C项目生成的.lib的文件名字 ,完成后确定

 我们再次运行自己的cpp程序,发现还是会发生链接错误,这又是什么原因?

在这里就涉及到了我们上面所讲的C++中独有的函数名规则,在C++调用C时,C里面是没有这个规则的,找的时候是用C++的方式找的,所以还是找不到,发生链接错误

不用慌,这是我们需要使用extern C,将我们的头文件包括里面

extern "C" {
#include"../StackC++_C 7-9/StackC++_C 7-9/Stack.h";
}

这时我们再次运行就可以通过啦,能通过的原因是在我们使用extern C后,C++会将函数按照C的风格来编译,因为C++兼容C。

5.2 C程序调用C++库

前面几部的流程和C++调用C类似,采取相反的操作即可,在这里就不再重复的赘述,有所改变的是在最后一步,C是不兼容C++的,我们只能去改CPP

还是使用extern C,下图是一种方式

 另一种方式,将你的头文件括起来

//C++的宏
#ifdef __cplusplus//_ _有俩
extern "C" //在C里面编译就不显示了
{
#endif // _cplusplus
	//初始化
	void StackInit(ST* ps);

	//插入
	void StackPush(ST* ps, STDataType x);

	//删除
	void StackPop(ST* ps);

	//销毁
	void StackDestory(ST* ps);

	//判空
	bool StackEmpty(ST* ps);

	//容量
	int StackSize(ST* ps);
	
	//返回栈顶元素
	STDataType StackTop(ST* ps);
#ifdef __cplusplus
}
#endif // _cplusplus

6、内联函数

内联函数的作用我们可以参考宏的优点

宏的优点:代码可维护性、宏函数提高效 率,减少栈帧的建立
# define ADD(a,b) ((a)+(b))
宏的缺点:可读性差、没有类型安全的检查 (直接替换)、不方便调试(调试时看不见)、复杂
以inline 修饰 的函数叫做内联函数, 编译时 C++ 编译器会在 调用内联函数的地方展开 没有函数压栈的开销 , 内联函数提升程序运行的效率。
  1. inline是一种以空间换时间 的做法,省去调用函数额开销。所以 代码很长 或者有 循环 /递归的函数不适宜使用作为内联函数。
2. inline对于编译器而言只是一个建议 ,编译器会自动优化, 如果定义为inline的函数体内有循环/递归等,编译器优化时会忽略掉内联。
3. inline不建议声明和定义分离 ,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到 。(不理解可以看函数重载处的图)
#include <iostream>
using namespace std;

inline void f(int i);

void T(int i) {
   cout << i << endl; 
}


//.c文件
int main()
{
   T(1);
   return 0; 
}
综上所述,内联函数适用被频繁调用且代码长度较短的函数

7、空指针

在良好的 C/C++ 编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的 错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{

 int* p1 = NULL;
 int* p2 = 0;
 
}

NULL指针实际上是一个宏,在C中的头文件stddef.h中

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

NULL可能被定义为字面常量0,或者被定义为无类型指针(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(int*) 函数,但是由于 NULL 被定义成 0 ,因此与程序的初衷相悖。
C++98 中,字面常量 0 既可以是一个整形数字,也可以是无类型的指针 (void*) 常量,但是编译器默认情况下将其看成是一个整形常量,如果要将其按照指针方式来使用,必须对其进行强转(void *)0
解决方法, 使用nullptr表示空指针

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值