C语言指针详解

 1为什么使用指针

1.使用指针型变量在很多时候占用更小的内存空间。
2.变量为了表示数据,指针可以更好的传递数据。
(显然,方案二更好一些,方案二类似使用指针传递地址,方案一将内存中的内容重新“复制”了一份,效率比较低。)
3.在数据传递时,如果数据块较大,可以使用指针传递地址而不是实际数据,即提高传输速度,又节省大量内存。
4.数据转换,利用指针的灵活的类型转换,可以用来做数据类型转换.
5.指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用数据形式
6.数据转换,利用指针的灵活的类型转换,可以用来做数据类型转换,比较常用于通讯缓冲区的填充。
7.指针的机制比较简单,其功能可以被集中重新实现成更抽象化的引用数据形式
8.函数指针,形如: #define PMYFUN (void*)(int,int) ,可以用在大量分支处理的实例当中,
9.在数据结构中,链表、树、图等大量的应用都离不开指针

2 指针是什么?

操作系统将硬件和软件结合起来,给程序员提供的一种对内存使用的抽象,这种抽象机制使得程序使用的是虚拟存储器,而不是直接操作和使用真实存在的物理存储器。
所有的虚拟地址形成的集合就是虚拟地址空间。
 
内存是一个很大的线性的字节数组,每个字节固定由 8 个二进制位组成,每个字节都有唯一的编号.
当程序使用的数据载入内存时,都有自己唯一的一个编号,这个编号就是这个数据的内存指针地址。指针就是这样形成的。

指针不仅可以表示变量的地址,还可以存储各种类型数据的地址,指针变量是用来保存这些地址的变量,
与数组类似,依据地址存放的数据类型,指针也分为 int 指针类型,  double 指针类型, char 指针类型等等。


综上,指针的实质就是数据在内存中的地址,而指针变量是用来保存这些地址的变量。
C语言指针详解_第1张图片

3.指针变量 和 指向关系

用来保存 指针 的变量,就是指针变量。

int num = 97;
int* p = & num; 指针变量p保存了变量 num的地址,p指向了变量num,p指向了num所在的内存块,
int **pp = &p;  指针变量pp指向了p所在的内存块, 

 int型的num值为97占4个字节,内存地址为:0113F924 

 C语言指针详解_第2张图片

  • num的地址为:0113F924num的值为 97 ,指针 p 指向 num 的内存块,指针 p 地址为:0113F90Cp的内存保存的值就是num的地址0113F924

 C语言指针详解_第3张图片

 

  • 指针变量 pp 指向 指针 p,指针 pp 内存值为 指针 p 的地址:0113F90C,形成了指向指针的指针。

 C语言指针详解_第4张图片

 定义指针变量

C语言中,定义变量时,在变量名 前 写一个 * 星号,这个变量就变成了对应变量类型的指针变量。必要时要加( ) 来避免优先级的问题。

引申:C语言中,定义变量时,在定义的最前面写上typedef ,那么这个变量名就成了一种类型,即这个类型的同义词。

取地址

既然有了指针变量,那就得让他保存其它类型变量的内存地址,使用& 运算符取得一个变量的地址。

特殊的情况,他们并不一定需要使用&取地址:
    
    数组名的值就是这个数组的第一个元素的地址。arr=&arr
    函数名的值就是这个函数的地址。  add=&add; 
    字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址。
    

#include 
using namespace std;


int add(int a , int b)
{
    printf("a+b=%d\n", a + b);
    return a + b;
}


int main(){
    int* a ; //int* 变量a
    int* p_int; //指向int类型变量的指针 p_int
    double* p_double; //指向double类型变量的指针 p_double
    struct Student* p_struct; //结构体类型的指针p_struct
    int** p_pointer; //指向 一个整形变量指针的指针




    int num = 97;//int 类型变量num
    int* p_num = #// 指针变量p_num  保存了变量num的内存地址&num

    float score = 10.00F;//float 类型的变量score
    float* p_score = &score; // 指针变量p_score 保存了变量score的内存地址&score




     int arr[3] = {1,2,3}; //arr是包含3个int元素的数组变量
     arr; &arr;//int类型的数组arr的内存地址arr   &arr
    int(*p_arr)[3]=&arr; //一个指向   含有3个int元素的数组arr   的指针变量 p_arr  【数组arr的指针变量p_arr】
    printf("指针变量p_arr=%p\n",p_arr); //指针变量p_arr=00A4FEF8

    // 或者这样写
    int(*p_arr2)[3];
     p_arr2=&arr;
    printf("指针变量 p_arr2 =%p\n",p_arr2); //指针变量 p_arr2 =00A4FEF4




     int(*p_func)(int,int);  //指向返回类型为int,有2个int形参的函数的指针变量 p_func  【函数add的指针变量p_func】
      add; &add; //函数add的内存地址add 或者 &add
     // 指针变量p_func 指向 函数add的内存地址add
     p_func=add;
     add(1,2); // a+b=3
    // 指针变量p_func 指向 函数add的内存地址 &add
    p_func=&add;
    add(2,2);  //a+b=4

   // 也可以这样写
    int(*p_funcc)(int,int)=&add;
    add(3,2); // a+b=5
}

解地址

 对一个指针解地址,就可以取到这个内存数据,解地址 的写法,就是在指针的前面加一个 * 号。
    解指针的实质是:从指针指向的内存块中取出这个内存数据。

 int age = 19;
    int*p_age = &age;
    *p_age  = 20;  //通过指针修改指向的内存数据

空指针

  空指针在概念上不同于未初始化的指针。
    空指针可以确保不指向任何对象或函数;
    而未初始化的指针则可能指向任何地方。空指针不是野指针。

在C语言中,我们让指针变量赋值为NULL表示一个空指针,而C语言中,NULL实质是 ((void*)0) ,
    在C++中,NULL实质是0。

void*类型指针

void是一种特殊的指针类型,可以用来存放任意对象的地址。一个void指针存放着一个地址,这一点和其他指针类似。不同的是,我们对它到底储存的是什么对象的地址并不了解。

由于void是空类型,只保存了指针的值,而丢失了类型信息,我们不知道他指向的数据是什么类型的,只指定这个数据在内存中的起始地址,如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。

 

double a=2.3;
	void *p=&a;
	cout<

 4.数组和指针

 同类型指针变量可以相互赋值,数组不行,只能一个一个元素的赋值或拷贝
    数组在内存中是连续存放的,开辟一块连续的内存空间。数组是根据数组的下进行访问的。
    指针很灵活,它可以指向任意类型的数据。指针的类型说明了它所指向地址空间的内存。
    
    
    数组所占存储空间的内存:sizeof(数组名)
    数组的大小:sizeof(数组名)/sizeof(数据类型),

    在32位平台下,无论指针的类型是什么,sizeof(指针名)都是 4 ,
    在 64 位平台下,无论指针的类型是什么,sizeof(指针名)都是 8 。
    
    
     数组名作为右值的时候,就是第一个元素的地址
    
    
    指向数组元素的指针 支持 递增 递减 运算。
    p= p+1意思是,让p指向原来指向的内存块的下一个相邻的相同类型的内存块。
    在数组中相邻内存就是相邻下标元素。

 int arrr[5] = {1,2,3,4,5};
    int* p_first = arr; //  数组名arrr作为右值的时候,就是第一个元素的地址
    int first =*p_first;
    printf("输出数组arrr第一个元素first=%d\n",first); //输出数组arrr第一个元素first=1
     p_first++;
    int second =*p_first;
    printf("输出数组arrr第二个元素second=%d\n",second); //输出数组arrr第二个元素second=2

5.函数与指针

 (1)函数的参数和指针

C语言中,实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,而不是同一个内存数据对象。这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。

C语言中,实参传递给形参,是按值传递的,也就是说,函数中的形参是实参的拷贝份,形参和实参只是在值上面一样,
    而不是同一个内存数据对象。
    这就意味着:这种数据传递是单向的,即从调用者传递给被调函数,而被调函数无法修改传递的参数达到回传的效果。

   有时候我们可以使用函数的返回值来回传数据,在简单的情况下是可以的,
    但是如果返回值有其它用途(例如返回函数的执行状态量),
    或者要回传的数据不止一个,返回值就解决不了了。

void change(int a)
{
    a++;      //在函数中改变的只是这个函数的局部变量a,而随着函数执行结束,a被销毁。age还是原来的age,纹丝不动。
}


//传递变量的指针可以轻松解决上述问题。
void change(int* pa)
{
    (*pa)++;   //因为传递的是age的地址,因此pa指向内存数据age。当在函数中对指针pa解地址时,
               //会直接去内存中找到age这个数据,然后把它增1。
}



int main(void)
{
    int age = 60;
    change(age);
    printf("age = %d",age);   // age = 60
    change(&age);
    printf("age = %d",age);   // age = 61
    return 0;
}

(2)函数的指针

一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址。
     我们可以把函数的这个首地址赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。
    这种指针就是函数指针。

函数指针的定义形式为:
    returnType (*pointerName)(param list);

 returnType 为函数返回值类型

 pointerNmae 函数指针名称

 param list 为函数参数列表 【参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称,这一点和函数原型非常类似。】

6.结构体和指针

  结构体指针有特殊的语法: -> 符号
    如果p是一个结构体指针,则可以使用 p ->【成员】 的方法访问结构体的成员

  

typedef struct
{
    char name[31];
    int age;
    float score;
}Student;

int main(void)
{
    Student stu = {"Bob" , 19, 98.0};
    Student*ps = &stu;

    ps->age = 20;
    ps->score = 99.0;
    printf("name:%s age:%d
",ps->name,ps->age);
    return 0;
}

 

7.深拷贝和浅拷贝

如果2个程序单元(例如2个函数)是通过拷贝 他们所共享的数据的 指针来工作的,这就是浅拷贝,因为真正要访问的数据并没有被拷贝。
    
    如果被访问的数据被拷贝了,在每个单元中都有自己的一份,对目标数据的操作相互 不受影响,则叫做深拷贝。



#include 
#include 

using namespace std;


class CopyDemo
{
public:
    int a;  //定义一个整型的数据成员
    char *str; //字符串指针  //一般结构体成员有指针 要自定义深拷贝构造函数,

   CopyDemo(int a, char * str)   //构造函数,两个参数
    {
      this->a=a;
    //  this->str=str;
    this->str = new char[1024]; //指针数组,动态的用new在堆上分配存储空间
     strcpy(this->str,str);    //拷贝过来
    }

    // C++会自动默认  帮我们实现一个浅拷贝制构造函数,浅拷贝只复制指针, 有问题使用深拷贝
    //浅拷贝
   /* CopyDemo(CopyDemo& obj){
        this->a=obj.a;
        this->str=obj.str; //这里是浅拷贝会出问题,要深拷贝
   }*/

    //一般结构体成员有指针 要自定义深拷贝构造函数,
   CopyDemo(CopyDemo& obj){
       this->a=obj.a;
      //  this->str=obj.str; //这里是浅拷贝会出问题,要深拷贝
        this->str = new char[1024]; //指针数组,动态的用new在堆上分配存储空间
      if(this->str!=0){
          strcpy(this->str,obj.str);    //如果成功,把内容深拷贝过来
      }
   }

    //析构函数
      ~CopyDemo() {
        delete str;
    }
};

int  main(){

    CopyDemo A(100,"hello");
    CopyDemo B = A;  //复制构造函数,把A的10和hello!!!复制给B
    cout <<"A.a="<< A.a << ",A.str=" <

你可能感兴趣的:(C++,c语言)