c++入门

简介: c++入门

文章目录


命名空间

解决c语言命名冲突问题

1.我们自己定义的变量,函数可能和库里面重名冲突

2.但是进入公司项目组里面,做的项目通常比较大,多人协助,会导致代码中命名冲突 c语言无法解决这个问题,除非换名字 c++提出了新语法,叫命名空间,

3.命名空间里面可以包含各种东西,函数,结构体,变量

#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
  printf("%d", rand);//如果没加stdlib.h这个头文件的化,
  //是可以正常运行的,但是如果包了这个头文件时,rand就是里面的函数
  //因为在预处理的时候,头文件展开,里面有rand这函数
  printf("%d", rand);//以十进制方式打印,出来的是地址
  return 0;
}

namespace空间-----是一个域

#include<stdio.h>
//定义了一个叫b的命名空间,命名空间是全局的
namespace b
{
  //他们还是一个全局变量,放到静态域,
  int rand = 0;//加这个就是为了避免冲突
  int a = 1;
}
int a = 0;//全局域
int main()//函数里面叫局部域
//编译器会先在局部去找,再去全局去找,否则就会报错
{
  printf("%d\n", rand);
  printf("%d\n", b::rand);//::叫做域作用限定符,意思是去取左边这个域里面的,很明显要去b这个域里卖弄去找
  int a = 1;
  printf("%d\n", a);//a为局部域里面去找
  printf("%d\n", ::a);//a到左边的空白域里面去找,就是去全局域里面去找
  return 0;
}

namespace也可以嵌套包含

对嵌套包含的要用两层::

//命名空间里面还可嵌套
namespace bt//命名空间里面不仅可以定义变量,还可以定义函数,类型
{
  int rand = 10;
  int add(int left, int right)
  {
    return left + right; 
  }
  struct node
  {
    int val;
    struct  node* next;
  };
  namespace n1
  {
    int d = 2;
  }
}
//使用命名空间里面的的东西
//不同文件里面定义的同样的命名空间,会被合并成一个,命名空间
int main()
{
  bt::rand = 10;//使用其与其他进行隔离
  struct bt:: node nt;//要加bt::才可以找得到里面的东西
  bt::add(1, 2);//这样就可以找到了,只是影响了编译器找的规则
  bt::n1::d = 2;//运用指定,这样子可以把嵌套的引用出来
  //这种可以做到最好的命名隔离,
  return 0;
}

c++的输入输出

#include<iostream>
//输入输出流
//using namespace std;
//c++库的实现定义在一个std的一个命名空间中,
int main()
{
  cout << "hello world" << endl;
  return 0;
}
#include<iostream>
using std::cout;
using std::endl;
using std::cin;
int main()
{
  std::cout << "hello world" << std::endl;
  return 0;
}
#include<iostream>
using namespace std;
//cout可以自动识别类型
int main()
{
  cout << "hello world" << endl;//endl是用来换行的
  double d = 1.2;
  int k = 12;
  cout << d <<" "<<k<< endl;//自动识别类型不用%d,不可以控制小数点的位数,c++d输入输出可以混在一起写
  cin >> k >> d;//把数据流向k和d
  return 0;
}

缺省

全缺省和半缺省

//缺省参数
void func1(int a = 0)//如果func1里面传了参数,就和传的是一样的,如果func1里面没有传参数,相当于实参是0,形参是a
//把缺省值去当作实参传给a
{
  cout << a << endl;
}
void func2(int a = 1, int b = 2, int c = 3)//全缺省
{
  cout << a << " " << b << " " << c << endl;
}
//半缺省,缺省一部分参数,必须从右往左缺省,必须连续缺省
void func3(int a,int b=32,int c=89)
{
  cout << a << " " << b << " " << c << endl;
}
int main()
{
  func(1);
  func1();
  func2();//啥都不传
  func2(13, 42);//从左往右给
  func3(13);//a必须要传,缺省的可以不传
  return 0;
}
struct stack
{
  int* a;
  int top;
  int capacity;
};
void stackinit(struct stack*ps,int capcity=4)
{
  ps->a = (int*)malloc(sizeof(int) * capcity);
  ps->top = 0;
  ps->capacity = capcity;
}
/*int main()
{
  struct stack st;
  stackinit(&st);//不知道栈最多可以存多少数据,就用缺省值初始化
  stackinit(&st, 100);//知道最多可以存多少数据,就传值,减少增容次数,提高效率
  return 0;
}*/
//缺省参数不能同时在声名与定义中出现,推荐大家写在声名,方便查找

函数重载

函数重载:是函数的一种特殊情况,同一个函数有不同功能, 这些同名函数的形参列表(参数个数或类型或顺序)不同,用来处理实现功能类似的不同问题

函数名相同的不同函数

//1.参数的类型不同
int add(int left, int right)
{
  cout<<left << " " << right << endl;
  return left + right;
}
double add(double left, double right)
{
  cout << left << " " << right << endl;
  return left + right;
}
//2参数个数不一样
void f()
{
  cout << "f()" << endl;//没传参数就调用他
} 
void f(int a)
{
  cout << "f(int a)" << endl;//传了参数就调用他
}
//顺序不同
void fs(int a, char b)
{
  cout << " fs(int a, char b)" << endl;
}
void fs(char b,int a)
{
  cout << " fs(char b,int a)" << endl; 
}
//返回值不同不能构成承载,返回值不同不能区分,参数不同可以区别的
//void a(int a)
//int a(int a)
//缺省值不同,不能构成重载
//1.
//void cz(int a)
//{
//  cout << "as" << endl;
//}
//void cz(int a = 21)
//{
//  cout << "asd" << endl;
//}
//2.形参个数不同,构成重载,但是使用的时候要小心
void as()
{
  cout << "ead" << endl;
}
void as(int a = 21)
{
  cout << "as" << endl;
}
int main()
{
  cout << add(3, 2) << endl;
  cout<<add(3.3, 8.3)<<endl;
  f();
  f(1);
  fs(2, 's');
  fs('a', 32);
  as();//存在歧义
  return 0;
}

引用

引用就是给别的变量取别名

int main()
{
  int a = 10;
  int b = a;//把a的值赋给b
  //引用定义。
  int& c = a;//引用,在变量名和类型的中间就是一个引用,是a的引用,
  //c是a的别名
  //c和a的地址和值都是一样的
  //引用在语法层,我们要理解为这里没有开一个新空间,就是对原来的取了一个新名称叫做b
  //如李逵,黑旋风都是一个人
  int a = 20;//a和c都改成20,
  int c = 30;//c和a都改成了30,
  //如李逵吃饭了,黑旋风也吃了
  //黑旋风吃饱了,李逵也吃饱了
  int* p = &a;//单独就是取地址
  return 0;
}
int main()
{
  int a = 0;
  int& b = a;//相同类型不会产生临时变量
  double c = 1.1;
  int m = c;//会产生一个临时变量,
  //同样类型的截断,类型的提升都会产生一个临时变量
  //函数的返回
  //传值返回也是临时变量
  const int& r = c;//r是c的临时变量的别名,把精度丢了
  cout << r<<m << endl;
  return 0;
}

引用的特性

1.引用必须初始化

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

3.引用只能用一个实体

int main()
{
  //1.引用在定义的时候必须初始化,引用是取别名,要说清楚是给谁取别名
  int a = 10;
  int& b = a;
  //2.一个变量可以有多个引用,
  int k = 3;
  int& kt = k;
  int& ktt = k;//相当于可以有很多个外号,
  int& m = kt;//这样也可以,kt,k,m,kkt都是一个空间,一个人可以有很多个名字
  //3.引用一旦引用了一个实体,再不能引用其他实体了,
  int d = 10;
  int& dt = d;
  int c = 20;
  //1.这里有两层,是让dt变成c的别名呢?     否
  //还是把c赋值给dt呢?                    是
  //经过调试是把c赋值给dt,dt和d的值都变成了20
  dt = c;
  return 0;
}

引用的应用

1.引用做函数参数

void swap(int* a, int* b)
{
  int tmp = *a;
  *a = *b;
  *b = tmp;
}
//cpp的实现
//1.做参数
void swap(int& r1, int& r2)//r1是x的引用,x的别名,传引用,传地址和传引用功能相同 ,形参的改变可以改变实参
{
  int t = r1;
  r1 = r2;
  r2 = t;
}
//传值和传地址,传引用构成重载,但是调用时,传值和传引用存在歧义,不知道是传值还是传引用,
int main()
{
  int x = 0, y = 1; 
  swap(&x, &y);
  swap(x, y);//传引用  
  return 0;
}
void slistpushback(slistnode*& phead, slistdate x)//指针的引用,别名不混在一起,phead的改变就是改变plist,不用传二级指针,好认识
{
  slistnode* newnode = buynode(x);
  //那么我们这个时候要找尾
  //找到尾节点的指针
  if (phead == NULL)
  {
    phead = newnode;
  }
  else
  {
    slistnode* tail = phead;//我们要让tail走到尾部去,而非走到空
    while (tail->next != NULL)
    {
      tail = tail->next;
    }//找到了尾节点,链接新节点
    tail->next = newnode;
  }
}
//指针也可以使用引用
int main()
{
  int a = 10;
  int& b = a;
  int* p = &a;
  int*& p2 = p;
  //p2也是地址,p也是地址,p2是p的别名
  return 0;
}
int* single(int* num, int numsize, int& returnsize)//returnsize就是传回去的长度的别名
{}

2.引用做返回值

传值返回会有拷贝,就会有空间

传引用返回就不会有拷贝,原样返回

如果使用的变量在函数使用完之后没有销毁或者返回的空间较大,就用引用返回

int dd(int a, int b)
{
  int c = a + b;
  return c;//返回的是c的临时拷贝,相当于int tmp=3被传回去了,只能调用一次,调用完了之后就会变成随机值
}
int& d(int a, int b)
{
  int c = a + b;
  return c;//f这里返回的就是c别名,
  //c传给int& tmp,就是c,所以不会有拷贝造成的空间浪费
}
//但是如果函数返回的时候,返回的对象出了函数的作用域还没有销毁,就可以用传引用返回
int& cout()
{
  static int n = 12;
  n++;
  //……
  return n;//n是一个静态变量,出了函数域不会消失,所以用传引用返回
}
int main()
{
  int ret = dd(1,2);
  //int& ret=dd(1,2),这样是不行的
  int r = d(1, 2);
  //这样写会存在非法访问,因为add(1,2)的返回值是c的引用,所以add栈帧销毁了以后,回去访问c位置的空间
  //如果add函数栈帧销毁了,清理空间那么取c值的时候,取到的就是随机值,给ret就是随机值,当然这个取决于编译器的的实现
  cout << ret << endl;
  cout << r << endl;
  return 0;
}
struct stack
{
  int* a;
  int top;
  int capacity;
};
void func1(struct stack st)//传值参数,会有拷贝,有空间开辟,原样拷贝
{
}
void func(struct stack* st)//传地址参数,指针开辟了4个字节
{
}
//引用做参数,随时都可以,可以1.提高效率,2.形参的修改可以影响实参,输出型参数
void func(struct stack& rst)//传引用参数,rst就是st的别名,没有开辟空间
{
}
  int main()
{
  struct stack st;
  func1(st);
  //指针和引用可以构成函数重载,因为指针和引用是两种不同类型的参数
  func(&st);
  func(st);
  return 0;
}
//传引用返回的价值
//1.提高效率,2.修改返回变量
int& at(int i)//处理作用域,还在就很有用
{
  static int a[10];//第一次进入就创建了一个数组
  return a[i];//返回这数组第i个位置的别名
  //变成了可读可写的,
}

常引用

用const进行修饰,在做参数的时候尽量都加一个const进行修饰

//尽量使用const引用进行传参
 void f(const int& a)
{
  cout << a << endl;
}
//const 引用通吃
// const type& 可以接收各种类型的对象
int main()
{
  //权限被放大了,a从一个只读不写的,变成b一个可读可写的就是错的
  //const int a = 10;
  //int& b = a;
  //权限不变,就可以
  const int a = 10;
  const int& b = a;//
  //权限缩小
  int c = 2;
  const int& d = c;//从可读可写的变成只读不写的是可以的
  double d = 1.11;
  int i = d;//类型转化,把d的整形部分转过去,不加引用可以
  //int& k = d;//
  //d的临时变量是具有常性,只读不写,而int&具有可读可写, 
  //普通引用引的是左值,const引用引的是右值
  //临时变量具有常性,不能被修改,
  //const 接收就是常量了
  return 0;
}


引用的优点:

引用和指针的不同点

1.引用在概念上定义一个变量的别名(没有开空间),指针存储一个变量的地址

2.引用在定义的时候必须初始化,指针最好初始化,但是不初始化也不会报错(会变成野指针)

3.引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个相同类型的实体(如我们在链表的时候指向不同的地方)

4.没有NULL的引用,但是有NULL 的指针

5.在sizeof里面的含义不同,引用为引用类型的大小,但是指针是地址空间所占字节大小(32位下是4个字节或64位里面的8个字节)

6.引用自加**++是引用的那个实体+1**,而指针是指针指向偏移一个类型的大小

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

8.有多级指针没有多级引用,引用比指针更加安全


extern “C”

c++程序,调用c的库,再c++程序中加extern "C"

告诉c++编译器,{}里面的函数是用c的编译器编译的,链接的时候,用c的函数名规则去找,就可以链接上了,只有c++才认识extern “c”


c程序,调用c++的库,再c++库中加extern “C”

c掉c++,c++的静态库就会用c的函数名修饰规则去处理以下函数,所以就不能用重载

//在c程序调用c++时候,在c++库里面加extern "C"
#ifdef  __cplusplus
#define EXTERN_C extern "C"
#else
#define EXTERN_C
#endif //  __cplusplus

内联函数inline

我们在每次调用函数都会建立栈帧,有栈帧就会有消耗,需要建立栈帧,栈帧中要保存一些寄存器,结束后又要恢复,

可以看到这些都是有消耗的,对于频繁调用的小函数,能否优化一下呢,

c语言可以用宏,来操作避免栈帧的建立

#define ad(x,y) ((x)+(y));

c++的实现方法inline,内联函数


有了inline就不需要去用c的宏,因为宏很复杂,很容易出错 inline但是展开的话,指令就会特别多

1.inline是一种以空间换时间的做法,省去调用函数栈帧的过程,所以代码量长的函数,或者递归函数就不适合用inline,展开之后就会有特别多的指令,变得很大

所以小函数比较适合用inline

2.inline对于编译器只是一个建议,编译器会自动识别,递归或长的编译器会自动忽视inline

3.inline函数声名和定义不能分离,.h和.cpp都要加一个inline,


结论:短小或频繁使用的函数就定义成inline

inline int add(int x, int y)//这样就不会建立栈帧,内联函数,默认是不会展开的,没有函数调用了
{
  int ret = x + y;
  return ret;
}
inline void swap(int& a, int& b)//swap也会经常会出现
{
  int tmp = a;
  a = b;
  b = tmp;
}
相关文章
|
28天前
|
安全 编译器 程序员
【C++初阶】C++简单入门
【C++初阶】C++简单入门
|
2月前
|
程序员 C++
C++模板元编程入门
【7月更文挑战第9天】C++模板元编程是一项强大而复杂的技术,它允许程序员在编译时进行复杂的计算和操作,从而提高了程序的性能和灵活性。然而,模板元编程的复杂性和抽象性也使其难以掌握和应用。通过本文的介绍,希望能够帮助你初步了解C++模板元编程的基本概念和技术要点,为进一步深入学习和应用打下坚实的基础。在实际开发中,合理运用模板元编程技术,可以极大地提升程序的性能和可维护性。
|
8天前
|
编译器 Linux C语言
C++基础入门
C++基础入门
|
1月前
|
安全 编译器 C++
C++入门 | 函数重载、引用、内联函数
C++入门 | 函数重载、引用、内联函数
24 5
|
1月前
|
存储 安全 编译器
C++入门 | auto关键字、范围for、指针空值nullptr
C++入门 | auto关键字、范围for、指针空值nullptr
47 4
|
1月前
|
编译器 C语言 C++
C++入门 | 命名空间、输入输出、缺省参数
C++入门 | 命名空间、输入输出、缺省参数
33 4
|
1月前
|
编译器 程序员 C语言
C++入门
C++入门
29 5
|
27天前
|
安全 编译器 C语言
C++入门-数组
C++入门-数组
|
27天前
|
存储 编译器 程序员
C++从遗忘到入门
本文主要面向的是曾经学过、了解过C++的同学,旨在帮助这些同学唤醒C++的记忆,提升下自身的技术储备。如果之前完全没接触过C++,也可以整体了解下这门语言。
|
2月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)