• C++细节补充


    1、带参数的宏

    关于#define的具体用法:

      #define命令是C语言中的一个宏定义命令,它用来将一个宏名替换为一个字符串,该标识符被称为宏名,被定义的字符串称为替换文本。宏定义只做字符替换,不分配内存空间。

    该命令有两种格式:一种是简单的宏定义,另一种是带参数的宏定义。


    (1)简单的宏定义:
    1. #define <宏名>  <字符串>
    2. 例: #define PI 3.1415926
    (2) 带参数的宏定义
    1. #define <宏名> (<参数表>) <宏体>
    2. 例: #define swap(a,b)(int c;c=a;a=b;b=c;)
        一个标识符被宏定义后,该标识符便是一个宏名。这时,在程序中出现的是宏名,在该程序被编译前,先将宏名用被定义的字符串替换,这称为宏替换,替换后才进行编译。
     

    2、二重指针和指针引用

    问题抛出:

    在函数的使用过程中,我们都明白传值和传引用会使实参的值发生改变。那么能够通过传指针改变指针所指向的地址吗

    在解决这个问题之前,先了解指针非常容易混淆的属性:
      ①.指针变量地址(&p)
      ②.指针变量指向的地址(p,存储数据的地址)
      ③.指针变量指向的地址的值(*p)
     
    指针就是存放地址的变量,可以简单的理解为:指针就是一个地址,
    指向指针的指针变量称为二级指针
     
    如果p是一级指针,pp是一个二级指针,那么有如下属性:
      ①.(&pp):二级指针的地址
      ②.(pp):二级指针指向的地址,即&p,也是传入后用到的参数
      ③.(*pp):二级指针里保存的地址 ,*pp即*&p,也就是p,存储数据的地址
      ④.(**pp):二级指针的地址保存的地址,该地址里面保存的地址里面的数据,即*p,内存空间里的值
    二级指针指向的地址存储的值就是一级指针指向的地址

     函数参数传递的只能是数值,所以当指针作为函数参数传递时,传递的是指针的值,而不是地址。

    当指针作为函数参数传递时,在函数内部重新申请了一个新指针,与传入指针指向相同地址。在函数内部的操作只能针对指针指向的值。

    #include <iostream>
    using namespace std;
     
    void pointer(int *p)
    {
        int a = 11, c = 33;
        printf("
    
    Enter function");
        printf("
    the p is point to  %p , p's addr is %X, *p is %d", p, &p, *p);
        *p = a;
        printf("
    the p is point to  %p , p's addr is %X, *p is %d", p, &p, *p);
        p = &c;
        printf("
    the p is point to  %p ,  p's addr is %X, *p is %d", p, &p, *p);
     
        printf("
    function return
    ");
    }
     
    int main()
    {
        int b = 22;
        int *p = &b;
     
        printf("the b address %X
    ", &b);
        printf("the p is point to %p , p's addr is %X, *p is %d", p, &p, *p);
        pointer(p);
        printf("
    the p is  point to %p , p's addr is %X, *p is %d
    ", p, &p, *p);
    }
    运行结果
    the b address 003DFC98 the p
    is point to 003DFC98 , p's addr is 3DFC8C, *p is 22 Enter function the p is point to 003DFC98 , p's addr is 3DFBB8, *p is 22//说明函数内产生了一个新的指针,和p一样指向同样的地址 the p is point to 003DFC98 , p's addr is 3DFBB8, *p is 11 the p is point to 003DFB98 , p's addr is 3DFBB8, *p is 33//函数内修改的都是新指针的地址 function return the p is point to 003DFC98 , p's addr is 3DFC8C, *p is 11//此时原来的指针p,地址不变,只改变了p指向内存的数值 请按任意键继续. . .

    虽然这个指针变量名字还是叫做p,但与main函数中的指针变量已经不一样了。
    这意味着,你可以改变main函数中b的值,但是不能改变p的值。

     又比如:

    #include <iostream>
    using namespace std;
     
    void GetMemory(char *p, int num)
    {
        p = (char*)malloc(sizeof(char)*num);
    }
     
    void main()
    {
        char *s = NULL;
        GetMemory(s, 100);
        strcpy(s, "hello");
        printf(s);
    }

    GetMemory这个函数是调用malloc申请一块内存。乍一看好像没什么问题,编译也不会报错。但是运行起来,程序直接奔溃。 其实有了上面的分析就可以知道,GetMemeory中的p是不能改变s的指向的,也就是说s还是指向NULL。GetMemory中的P是临时申请的一个指针变量,当s传值进来(NULL),时,p指向NULL,除此之外,没做任何改变。当运行malloc函数后,也只是将malloc返回的的指针地址赋给了p,并不能传递给s。所以这里就需要指针的指针(双重指针)了。

    #include <iostream>
    using namespace std;
     
    void GetMemory(char **p, int num)
    {
        *p = (char*)malloc(sizeof(char)*num);
    }
     
    void main()
    {
        char *s = NULL;
        GetMemory(&s, 100);
        strcpy(s, "hello
    ");
        printf(s);
    }

    这个时候就是将指针变量s的地址传递了过去,而不是将指针变量的值(NULL)传递了过去,因此就可以改变s的指向了。

    其次,还可以用指针引用来修改指针的地址。

    类似于普通变量传入变量引用,我们也传入一个指针引用,此时我们操作pp的值就是更改了p的值。

    void make(int *&pp)
    {
        pp=new int(66); //试图改变p指向的地址
    }
    int main()
    {
        int a=5;
        int *p=&a; //指针变量指向一个int类型的地址
        cout<<"address:"<<&a<<" value:"<<a<<endl;
        cout<<"address:"<<p<<" value:"<<*p<<endl;
        make(p);
        cout<<"address:"<<p<<" value:"<<*p<<endl;
    }

    类似的可以用二级指针修改p的内存

    void make(int **pp)
    {
        int * p=new int(66);
        *pp=p; //二级指针的解引用被赋值需要得到一个一级指针变量,上图中二级指针的示意图中  *pp=p
    }
    int main()
    {
        int a=5;
        int *q=&a;
        int **pp=&q;
        cout<<"address:"<<&pp<<" "<<pp<<" "<<&q<<" "<<q<<" value:"        <<*q<<endl;
        make(pp);
      cout<<"address:"<<&pp<<" "<<pp<<" "<<&q<<" "<<q<<" value:"<<*q<<endl;
    }

     3、重载、重写和重定义

    重载overload:是函数名相同,参数列表不同 重载只是在类的内部存在。但是不能靠返回类型来判断。
    重写override:也叫做覆盖。子类重新定义父类中有相同名称和参数的虚函数。函数特征相同。但是具体实现不同,主要是在继承关系中出现的 。
    重写需要注意:
    1 被重写的函数不能是static的。必须是virtual的
    2 重写函数必须有相同的类型,名称和参数列表
    3 重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public,protected也是可以的
     

    重定义 (redefining)也叫做隐藏:

    子类重新定义父类中有相同名称的非虚函数 ( 参数列表可以不同 ) 。

    如果一个类,存在和父类相同的函数,那么,这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用是不能成功的。
     

     4、vector和list的区别

    1.vector数据结构
    vector和数组类似,拥有一段连续的内存空间,并且起始地址不变。
    因此能高效的进行随机存取,时间复杂度为o(1);
    但因为内存空间是连续的,所以在进行插入和删除操作时,会造成内存块的拷贝,时间复杂度为o(n)。
    另外,当数组中内存空间不够时,会重新申请一块内存空间并进行内存拷贝。

    2.list数据结构
    list是由双向链表实现的,因此内存空间是不连续的。
    只能通过指针访问数据,所以list的随机存取非常没有效率,时间复杂度为o(n);
    但由于链表的特点,能高效地进行插入和删除。

    小结:

    vector拥有一段连续的内存空间,能很好的支持随机存取,
    因此vector<int>::iterator支持“+”,“+=”,“<”等操作符。

    list的内存空间可以是不连续,它不支持随机访问,
    因此list<int>::iterator则不支持“+”、“+=”、“<”等

    vector<int>::iterator和list<int>::iterator都重载了“++”运算符。

    总之,如果需要高效的随机存取,而不在乎插入和删除的效率,使用vector;
    如果需要大量的插入和删除,而不关心随机存取,则应使用list。

    5、C++中begin、end、front、back函数的用法

    一、begin函数

    函数原型:

    iterator begin();

    const_iterator begin();

    功能:

    返回一个当前vector容器中起始元素的迭代器。

     

    二、end函数

    函数原型:

    iterator end();

    const_iterator end();

    功能:

    返回一个当前vector容器中末尾元素的迭代器。

     

    三、front函数

    函数原型:

    reference front();

    const_reference front();

    功能:

    返回当前vector容器中起始元素的引用。

     

    四、back函数

    函数原型:

    reference back();

    const_reference back();

    功能:

    返回当前vector容器中末尾元素的引用。

    此外,back()还可以用于字符串类型,

    返回字符串的最后一个字符的引用。

     6、std::find()

     find函数主要实现的是在容器内查找指定的元素,并且这个元素必须是基本数据类型的。
    查找成功返回一个指向指定元素的迭代器,查找失败返回end迭代器。

    #include <iostream>
    #include <vector>
    #include <algorithm>
    using namespace std;
    int main(){
            vector<int> v;
            int num_to_find=25;//要查找的元素,类型要与vector<>类型一致
            for(int i=0;i<10;i++)
                    v.push_back(i*i);
            vector<int>::iterator iter=std::find(v.begin(),v.end(),num_to_find);//返回的是一个迭代器指针
            if(iter==v.end())
                cout<<"ERROR!"<<endl;
            else               //注意迭代器指针输出元素的方式和distance用法
                cout<<"the index of value "<<(*iter)<<" is " << std::distance(v.begin(), iter)<<std::endl;
            return 0;
    }

     6、类型别名、别名声明、auto类型说明符、decltype类型指示符

    某种类型的同义词,有两种方法可用于定义类型别名:
    1、类型别名typedef
    typedef int zhengxing;
    zhengxing i=0;
    2、别名声明using
    using SI = Sales_item;

    3、auto类型说明符

    让编译器去分析表达式所属的类型。

    auto 定义变量必须有初始值。

    4、decltype类型指示符

    选择并返回操作符的数据类型;

    基本用法:

    int getSize();
    
    int main(void)
    {
        int tempA = 2;
        
        /*1.dclTempA为int*/
        decltype(tempA) dclTempA;
        /*2.dclTempB为int,对于getSize根本没有定义,但是程序依旧正常,因为decltype只做分析,并不调用getSize,*/
        decltype(getSize()) dclTempB;
    
        return 0;
    }

     7、随机数的产生

    C++中没有自带的random函数,要实现随机数的生成就需要使用rand()和srand()。需要加头文件#include <cstdlib>

    不过,由于rand()的内部实现是用线性同余法做的,所以生成的并不是真正的随机数,而是在一定范围内可看为随机的伪随机数。

    rand()

    rand()会返回一随机数值, 范围在0至RAND_MAX 间。RAND_MAX定义在stdlib.h, 其值为2147483647。如果没有随机数种子,两次循环调用rand()所产生的随机数序列是一样的。

    若要求0-n的随机数

    rand()%(n-1)

    srand()

    srand()可用来设置rand()产生随机数时的随机数种子。通过设置不同的种子,我们可以获取不同的随机数序列。

    可以利用srand((int)(time(NULL))的方法,利用系统时钟,产生不同的随机数种子。不过要调用time(),需要加入头文件< ctime >。

    公式

    要取得[0,n)  就是rand()%n     表示 从0到n-1的数

    要取得[a,b)的随机整数,使用(rand() % (b-a))+ a; 
    要取得[a,b]的随机整数,使用(rand() % (b-a+1))+ a; 
    要取得(a,b]的随机整数,使用(rand() % (b-a))+ a + 1;

  • 相关阅读:
    Cocos2dx-lua开发之c++绑定到lua
    SCOPE_IDENTITY和@@IDENTITY[转]
    c#List结合IEqualityComparer求交集
    K:java中正则表达式的使用说明及其举例
    K:常见的正则表达式
    K:正则表达式之基础简介
    Q:javax.comm 2.0 windows下Eclipse的配置
    Q:同时安装了python2和python3的电脑下pip的使用
    K:java 断言 assert 初步使用:断言开启、断言使用
    请描述一下 cookies,sessionStorage 和 localStorage 的区别
  • 原文地址:https://www.cnblogs.com/juanjuanduang/p/10841251.html
Copyright © 2020-2023  润新知