【C++】初识Cplusplus

        别的不说,先认识祖师爷,本贾尼·斯特劳斯特卢普(Bjarne Stroustrup,1950年6月11日-),丹麦人,计算机科学家,在德克萨斯A&M大学担任计算机科学的主席教授。他最着名的贡献就是开发了C++程序设计语言。

        以后面试官问你C++之父是谁,就说是本贾尼博士,注意!可不是谭浩强哦。

        众所周知,C语言是面向过程的语言,大佬觉得不好用(Procedure Oriented 简称PO ),就引入了对象的概念,C++就说一门面向对象的编程语言(Object Oriented Programming)。

        C++三大特性:封装、继承、多态。

        普遍认为C++是为了填补一些C语言的坑,所以C++兼容C语言,下面来看第一个坑。

一、命名空间

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

        定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名空间的成员。

        

#include<stdio.h>
#include<stdlib.h>

int rand = 0;
//这里rand重定义了,名字冲突了。


//命名空间
namespace bit
{
    //变量
    int rand=0;
    
    //函数
    void func()
    {
        return ;
    }
    
    
   	//结构体
    struct TreeNOde
    {
        struct TreeNode* left;
        struct TreeNode* right;
        int val;
    };
    
}

//::域作用限定符  指定到bit里面去找
printf("%p\n",bit::rand);

        这里在全局定义一个变量rand,但是在C语言的库中已经有一个rand()函数,那么编译器去全局域去找的时候,就没办法确认该使用哪个。

        这时候就可以定义一个命名空间bit,把rand变量定义在里面,并且使用的时候指定到bit里面的rand变量。

        当然如果名字太长的话,指定起来就比较麻烦就可以把命名空间给展开

//展开C++标准库
using namespace std;

PS:在项目总尽量不要using namespace std;那么就可以只展开一部分

//只展开一部分
using std::cout;
using std::cin;

        命名空间也可以嵌套命名空间(这个一般不常用)

namespace sql
{
    namespace bit
    {
        int rand=0;
    }
}

printf("%p\n",sql::bit::rand);

二、缺省参数:

        声明函数的时候,给形参一个缺省值,如果传参的时候没有给与参数,那么默认参数就是这个缺省值。

void fun(int a=0)
{
	cout<<a<<endl;
}

int main()
{
	fun(1);
	fun(2);
	fun(3);
    fun()
	return 0;
}
//不传参数,相当于就用参数0

全缺省:

        全部的参数都给上缺省值。

void TestFunc(int a=10,int b=20,int c=3)
{
    //...
}

TestFunc(1);

TestFunc(,,1);//C++不支持这种语法

PS:这里要注意,如果只传一个参数,先传给的是参数a,不能隔着传参数,如果这里想传参数给b那么要先给a参数。   

半缺省:

        注意,半缺省的意思不是只给一半的缺省值。

void TestFunc(int a,int b=20,int c=30)
{
    //...
}  

PS:给缺省值的时候,必需从右往左给,并且连续缺省,不能有间隔。

PS:缺省参数不能在声明和定义里面同时出现

void  StackInit(struck Stack* ps,int capacity=4);
    
void  StackInit(struck Stack* ps,int capacity)
{
    ...
}
//缺省最好在声明中使用

三、函数重载:

        C++允许在同一个作用域下,声明同名的函数,这些同名函数的形参列表(参数个数 或 类型 或 顺序)必须不同。

int Add(int left,int right){}
    
double Add(double left,double right){}
    
Add(1,2)
Add(1.1,2.2)

PS:下面这种互换了新参位置,也算函数重载。

int Add(int a,char i){}

int Add(char i,int a){}

PS:但是要注意!函数的返回值不同,不算函数重载。

//这个不算函数重载

double Add(int a,char i){}

void Add(int a,char i){}

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

A:

        首先要知道一个程序编译的时候要执行以下的步骤:

         在汇编的时候,会形成符号表,C语言函数的符号表,一般其命名规则就是它自己的函数名,如果多了一个相同名字的函数,那么链接的时候,编译器就不知道该链接哪个。

        而在C++中多了一个,函数名修饰规则,其根据参数不同,修饰出来的名字就不同。

四、引用:

        给变量取别名,使用这个别名相当于在使用变量。

int a = 0;
int &b = a; // 引用

PS:要注意细节,引用的符号是(&),虽然跟取地址的符号是一样的,但是要注意这里只能定义的时候才能使用后这个符号,而且定义的时候必须进行初始化。

        一个变量可以有多个引用,但是一旦引用的就不能再引用别的变量了。

int a = 10;

int &b = a;

int &C = a;

int &d = c;

//一旦引用一个实体,再不能引用其他实体了
int x = 10;
//这里就不是引用了,而是赋值。
b=x; 

引用的价值:

1、做参数-- a、输出型参数 b、大对象传参,提高效率

void swap(int &r1,int &r2)
{
    int tmp=r1;
    r1=r2;
    r2=tmp;
}

void struct List
{
   //...
}SLNode,*PSLTNode;

//这三种写法的效果是一样的
void SListPushBack(SLNode** list,int x);

void SListPushBack(SLNode*  &list,int x);

void SListPushBack(PSLTNode&  list,int x);

2、做返回值-- a、输出返回对象 ,调用者能修改返回对象 b、减少拷贝,提高效率

int &count()
{
	int n=0;
    n++;
    //...
    return n;
}
//返回 返回对象n的别名

int& ret= count(); 
int& ret= count(); //这里调用第二次的时候返回值变为了乱码

//这里写法是错误的,函数栈帧结束后,清理栈帧置随机值,n被清除了。

int &count()
{
	static int n=0;
    n++;
    //...
    return n;
}
//这里可以返回,变量n是在静态区生成的,不会被清除。

        如果出了函数的作用域,函数对象就销毁了,一定不要用引用返回,一定要用传值返回。所以使用引用的时候一定要明白变量的作用域的范围,不要越界。

        引用的精华写法,调用者可以修改返回对象。

typedef struct SL
{
	int *a;
	int size;
	int capacity;
}SL;

void SLInit(SL& s,int capacity=4)
{
    s.a=(int *)malloc(sizeif(int)*capacity);
    if(s.a==NULL)
        return;
    s.size=0;
    s.capacity=capacity;
}  

void SLPushBack(SL& s,int X)
{
    if(s.size==s.capacity){
        int newCapacity=s.capacity*2;
		s.a=(int *)relloc(s.a,sizeif(int)*newCapacity);
    }
    
    s.a[s.size++]=X;
    
}

//返回对应位置的数
int &SLAt(SL& s,int pos,int X)
{
    assert(pos>=0 && pos<=s.size);
    
    return s.a[pos];
}


SL s;

SLInit(s);
SLPushBack(s,1);
SLPushBack(s,2);
SLPushBack(s,3);

SALt(s,1)++; //这里的引用对象就是 s.a[1],让其自增了1。
SALt(s,2)=10; //这里把 s.a[2]赋值为了2。

        引用时权限不能放大。

const int c=20;
//int &d=c; 不能把常量引用为非常量
const int& d=c;

        引用时权限可以缩小。

int e=30;
const int& f=e;

    

int ii=1;
double dd=ii;    //强制转换

//double& rdd=ii;    无法转换
const double& rdd=ii;    //加上一个const就能强制转换

        数据类型强制转换会产生成一个临时变量,然后再把临时变量赋值给变量,临时变量具有常性。rdd引用的是临时变量。

        强转不会改变数据类型。

        所以如果使用引用传参,函数内如果不改变参数原来的值,那么参数尽量用 const修饰。

引用很容易与指针混淆,它们之间有三个主要的不同:

  • 不存在空引用。引用必须连接到一块合法的内存。指针可以置空。
  • 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
  • 引用必须在创建时被初始化。指针可以在任何时间被初始化。

PS:从语法的角度来说,因为其是变量的别名,所以引用是不占空间的,但是指针是占4/8字节空间的。        

        事实上,C++中的引用类型其本质就是指针常量(int *const a),当我们使用引用时,编译器将会自动为我们定义一个指针常量,并将被取别名的变量的地址赋值给该指针常量,或者通过解引用指针常量来访问被取别名的变量。

        

五、内联函数:

        在C语言中有个宏的概念,大佬觉得宏不好用,就引入了内联函数。内联函数是用来替代宏函数。

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

  

inline Add(int a,int b)
{
	return a+b;
}
//走函数的机制,但是不创建栈帧

        如果inline的函数存在 递归、代码长,编译器会忽略掉内联。

PS:编译器Debug模式下面默认是不展开的。

PS:普通函数调用的时候要Call 函数地址,每次调用执行都是执行的同一个地址的代码。(这个方式省空间)

        内联函数是空间换时间的做法,省去函数开销。但是在调用的时候展开,把代码直接转换成汇编执行。每次调用都会展开一次,调用的越多,编译出来的可执行程序就越大。(因为代码变多了)

        内联不建议声明(.h)定义(.cpp)分离,会导致链接错误。内联函数展开时候不带函数名,符号表里面没有函数地址,用函数名调用的时候,会找不到函数。

六、auto关键字:

        其作用就是自动推导变量的类型。
 

int a=10;
auto b=a;

int x=10;
auto *a=&x; //int* 强调指针
auto& c=x;  //强调引用

//打印变量类型
typeid(b).name();

两个应用场景:

        1.遍历数组

int a[]={1,2,3,4,5,6,7,8,9};

//范围for     这里有个迭代器的概念
for(auto e:a)
{	
	cout <<e <<" " ;
}

//依次赋值给e,自动遍历,自动增加,自动判断结束

        这里e--,不会改变 a[] 里面的数据,只是a的拷贝。但是使用引用auto&e,e--会改变。

for(auto &e:a)
{
    //...
}

// auto *e:a 注意这里是错误的,不能用指针。

        2.类型比较长的时候,可以用auto自动推导类型。

        后面会学习SLT,有的变量的类型非常长,用auto会非常省事情。

//没展开std的话
std::map<std::string,std::string> dict;

//定义一个迭代器
std::map<std::string,std::string>::iterator it=dict.begin();

auto it=dict.begin();


拓展:

        C++的空指针是nullptr,就不要使用NULL了。

        C语言中的NULL是宏,其定义为0,是数字。某些地方会发生问题。

PS:这个定义是原来留下来的缺陷,但是定义了的东西,就不能更改了,不然会影响很多用户的使用(phyton就是前面版本的一些定义改了,导致后面的版本不兼容)

//重载
void f(int) {}
void f(int *) {}

int *p=NULL; 
f(0);   
f(NULL); // 宏是替换,这里就替换成了0
f(p);      
f(nullptr) 

        这里前两个调用会走第一个函数,后两个调用会走第二个调用。因为宏是替换,这里把NULL替换成了0。

猜你喜欢

转载自blog.csdn.net/weixin_45423515/article/details/125813457