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;

原文地址:https://www.cnblogs.com/juanjuanduang/p/10841251.html