入门c++学习

1.命名空间域——namespace

——>简单功能介绍:

       我们会有时会遇到以下代码:

#include<stdio.h>
int a = 0;
namespace num
{
  int a = 1;
}
int main()
{
  int a = 2; 
  printf("%d\n", a);
 return 0;
}

   这些代码当然是正确的,三个整形的名字相同也可以吗?因为他们在不同的范围内,所以当然是可以的,在一个范围内名字相同,这是不可以的。

     我们如果运行程序会发现,输出的是0,可以看出是有一定的顺序的——访问一个变量,顺序是局部域->全局域->展开了的命名空间域或者指定访问的命名空间域。在图中代码中,num相当于是一个命名空间域的名字,namspace则是命名空间域的关键字。

    对于上面说到的展开的命名空间域,演示如下:

在namespace num前面加上using后,相当于打开了这个命名空间域,那么这时候再去printf会发现一个问题,这个a发生了错误,那么我们可以得到展开了的命名空间域就类似于原本空间域里面的a暴露在了和全局变量一样的全局域里面。所以不要轻易的使用using。展开命名空间指的是编译时编译器是否会到里面去搜索。

   这个关键字使用来解决命名冲突的问题的,给出以下代码:

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

int rand = 0;

int main()
{
 printf("%d\n",rand);
 return 0;
}

乍一看下面的代码没有什么问题但是运行的时候就会报错,这是因为rand和stdlib头文件里面的一个函数名重合了,所以会发生这样的错误。正确的方式避免这种情况就是使用namespace。

把这个变量放在一个域里面,然后用::来找到它,::就是所谓的域作用限定域。::在这个符号左边加上命名空间域的名字,就可以找到里面的变量,如果左边是空白的没加东西,那么就会找到名字为a的全局变量

 ——>命名空间的定义:

    (1) 命名空间内可以定义变量,函数和类型等。

    (2)命名空间可以嵌套使用。

    (3)同一个工程中允许存在多个相同名称的命名空间,编译器最后会合并为同一空间。
      

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

——>命名空间的使用:

命名空间有三种使用方法:

   (1)加命名空间域名称以及作用域限定符:

int main()
{
   printf("%d\n", N::a );
   return 0;
}

这样就可以正确的使用到N这个命名域里面的a变量了。

   (2)使用using namespace来引入变量:

namespace N1
{
int a;
int b;
int Add(int left, int right)
 {
     return left + right;
}

using namespace N;

int main()
{
  printf("%d\n", b);
  return 0;
}

打开这个作用域后就可以直接使用了,但是在一些大型项目里面是不建议这样使用的。最好的方式应该是下一种:

  (3)使用using将命名空间中某个成员引入:

using N::b;
int main()
{ 
  printf("%d\n", b);
  return 0;
}

这样在打代码的过程中就可以把只把常用的东西给“放出来”。

2.c++的输出和输入

——>书写hello world:

  我应该如何用c++写一个hello world呢:

#include<iostream>
//std是c++标准库的命名空间名,c++将标准库的定义实现都放在这个命名空间里面。
using namespace std;
int main()
{
  cout<<"hello world!"<<end1;
  return 0;
}

说明:

(1)  使用 cout 标准输出对象 ( 控制台 ) cin 标准输入对象 ( 键盘 ) 时,必须 包含 < iostream > 头文件
以及按命名空间使用方法使用 std
(2)cout和 cin 是全局的流对象, endl 是特殊的 C++ 符号,表示换行输出,他们都包含在包含 <
iostream > 头文件中。
(3) << 是流插入运算符, >> 是流提取运算符
(4) 使用 C++ 输入输出更方便,不需要像 printf/scanf 输入输出时那样,需要手动控制格式。
C++ 的输入输出可以自动识别变量类型。
(5)  实际上 cout cin 分别是 ostream istream 类型的对象, >> << 也涉及运算符重载等知识。
注意: 实际上在用cout输出有多位小数的数字时,他有时候会省略几位小数,这时候我们可以c语言和c++混合使用。
  • 先包含头文件再使用 using namespace std;:这是常见的做法。先包含头文件,编译器能知道有哪些标识符可用,接着使用 using namespace std; 让这些标识符在当前作用域直接使用。
  • 先使用 using namespace std; 再包含头文件:这种顺序也可行。不过在包含头文件之前,由于没有引入具体的标识符,using namespace std; 暂时没有实际作用。只有在包含头文件之后,其中的标识符才会被引入到当前作用域。

——>std的使用惯例:

(1) 在日常练习中,建议直接using namespace std即可,这样就很方便。
(2) using namespace std展开,标准库就全部暴露出来了,如果我们定义跟库重名的类型/对
象/函数,就存在冲突问题。该问题在日常练习中很少出现,但是项目开发中代码较多、规模
大,就很容易出现。所以建议在项目开发中使用,像std::cout这样使用时指定命名空间 +
using std::cout展开常用的库对象/类型等方式。

3.缺省值

——>缺省值的概念:

缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
#include<iostream>
using namespace std;
void fun(int a = 0)
{
  cout<<a<<endl;
}

int main()
{
  fun();//使用缺省值
  fun(10);//使用给定的值
  return 0;
}

上述代码就是缺省值的一个具体使用案例,缺省值就相当于预先给定好的一个值。

——>缺省值的类型:

(1)全缺省值:

void add(int a = 5, int b = 10, int c = 20)
{ 
  cout<<a<<endl;
  cout<<b<<endl;
  cout<<c<<end1;
}

 如上面代码所示全缺省值就相当于所有的形参都给上了预定的值。

(2)半缺省值:

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

在上述代码中,形式参数a并没有给定预定的参数,这种形式就叫做半缺省值。

注意:(1)在全缺省类型中,你要调用add函数,如果你以此输入了自己想要的两个数字,那么就会按照顺序从左到右覆盖缺省值去使用。中间不能隔着给数字,没有这种定义。

          (2)在半缺省中,所给定的数字是从右往左给出,不能隔着给。

——>使用时的注意事项:

      如果在多文件中使用缺省值的话,只需要在函数的声明的时候给出缺省值。如果在声明和定义使用缺省值的话会报错。

// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该
用那个缺省值。
举例一下:
    Stack.h文件:
//Stack.h文件
#include<iostream>

typedef struct stack
{
  int* a;
  int top;
  int capacity;
}stack;

void stackinit(stack* pst ,int defaultcapacity = 40);

   stack.cpp文件:

#include"Stack.h"


void stackinit(stack* pst,int defaultcapacity)
{
  pst->a = (int*)malloc(sizeof(int)*defaultcapacity);
  if(a == NULL)
   {
     perror("malloc fail");
     return;
   }
 pst->top = 0;
 pst->capacity = defaultcapacity;
}

   在函数声明时给出缺省值就行了,在定义时不需要写出缺省值的大小。

4.函数重载:

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

#include<iostream>
using namespace std;

// 1、参数类型不同
int Add(int left, int right)
{
	return left + right;
}
double Add(double left, double right)
{
	return left + right;
}
int main() {
	cout << Add(1, 2) << endl;
	cout << Add(1.1, 3.3) << endl;
	return 0;
	}

5.引用:

引用的概念,就像当于取别名,别名和原本的名字指向同一块空间,我们学习的时候就当做引用时不开辟空间的。

引用 不是新定义一个变量,而 是给已存在变量取了一个别名 ,编译器不会为引用变量开辟内存空
间,它和它引用的变量 共用同一块内存空间。
                                 用法:       类型 & 引用变量名 ( 对象名 ) = 引用实体;
   
       
int a = 10;
int& b = a;
int& c = a;
int& d = b;

在上述代码中,bcd均是a的别名,改变bcd的值会改变a

注意:(1)引用类型必须和引用实体同种类型的。

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

          (3)一个变量可以有多个引用

          (4)引用一旦引用一个实体,就不可以再引用其他实体了。

(1)引用做参数:

           使用场景,可以在函数里面使用:假设我们要写一个交换函数。

swap(int& a , int& b)
{
  int tmp = b;
  b = a;
  a = tmp;
}

     我们使用引用,就不必要和指针一样麻烦,可以直接对对象进行操作。

(2)引用作为返回值:

下面以add函数为例:

int& Add(int a, int b)
{
    int c = a + b;
    return c;
}
int main()
{
    int& ret = Add(1, 2);
    Add(3, 4);
    cout << "Add(1, 2) is :"<< ret <<endl;
    return 0;
}

注意:引用的使用会涉及权限问题

      (1)权限放大:

const int a = 0;
int& b = a;

    通俗一点的解释就是变量a不能修改,但是b作为a的别名,它却有了可以更改的权力。

     (2)权限可以缩小或者平移:

int x = 0;
int& y = x;
const int& z = x;

  原本的变量可以修改,别名不能修改原本的值,这就是一种权限的缩小。

    (3)以下代码是值得注意的:

double d = 1.11;
int c = d;
int& r = d;

上述代码在编译器里面输入会发现是明显错误的,上述的代码存在隐式转换。用int类型的r去作为一个double类型的别名,会出现一个d的int类型的临时变量,转换过程都存在这种变量,这种临时变量具有常性,所以r存在权限放大的问题,是不对的,这段代码的最后一行变为const int& r = d;

就是一个正确的代码。

    (4)在函数使用中的问题:

int func()
{
  static x = 0;
  return x;
}

int main()
{
 int& ret = func();
}

在函数 返回时,返回的是一个和(3)中一样具有常性的临时变量,所以这里存在和(3)中一样的问题。

常规写法和使用引用的对比举例:

    常规顺序表(静态):

#include <stdio.h>
#include <assert.h>

typedef struct SeqList
{
    int a[100];
    int size;
} SeqList;

// 找到pos位置的值是什么
int sltfind(SeqList* ps, int pos)
{
    assert(ps != NULL);
    return ps->a[pos];
}
// 修改pos位置的值,变成x
void sltmodify(SeqList* ps, int pos, int x)
{
    assert(pos < 100 && ps != NULL);
    ps->a[pos] = x;
}

int main()
{
   
    return 0;
}

     使用引用改进这段顺序表:

#include<iostream>
#include<cassert>

typedef struct SeqList
{
 int a[100];
 int size;
}SeqList;

//找到pos位置的值
int& SLAt(SeqList* ps,int pos)
{
  assert(pos>=0 && pos<ps->size && ps!=NULL);
  return ps->a[pos];
}

int main()
{
   SeqList sl;
    sl.size = 3;
    sl.a[0] = 1;
    sl.a[1] = 2;
    sl.a[2] = 3;

    // 测试获取指定位置的值
    int value = SLAt(&sl, 1);
    std::cout << "位置 1 的值是: " << value << std::endl;

    // 测试修改指定位置的值
    SLAt(&sl, 1) = 10;
    std::cout << "修改后位置 1 的值是: " << sl.a[1] << std::endl;

    return 0;
 return 0;
}

总结:使用引用做返回值,我们会发现不仅减少了拷贝,还可以修改返回值+获取返回值

6.内联函数:

概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调 用建立栈帧的开销,内联函数提升程序运行的效率。
使用方法:
        在函数的声明前加上inline即可。

——>内联函数的特点:

(1). inline 是一种 以空间换时间 的做法,如果编译器将函数当成内联函数处理,在 编译阶段,会
用函数体替换函数调用 ,缺陷:可能会使目标文件变大,优势:少了调用开销,提高程序运
行效率。
(2). inline 对于编译器而言只是一个建议,不同编译器关于 inline 实现机制可能不同 ,一般建
议:将 函数规模较小 ( 即函数不是很长,具体没有准确的说法,取决于编译器内部实现 )
是递归、且频繁调用 的函数采用 inline 修饰,否则编译器会忽略 inline 特性。

(3)inline函数不建议声明和定义分开,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接的时候会找不到。

----->一个经典题目:

       宏的优缺点?
    优点:
       1. 增强代码的复用性。
       2. 提高性能。
    缺点:
     1. 不方便调试宏。(因为预编译阶段进行了替换)
     2. 导致代码可读性差,可维护性差,容易误用。
     3. 没有类型安全的检查 。
     C++ 有哪些技术替代宏
     1. 常量定义 换用 const enum
     2. 短小函数定义 换用内联函数
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值