龙空技术网

从用作函数参数和构造链表的角度来区分C指针、C++、Java引用

小智雅汇 146

前言:

而今姐妹们对“用java实现单链表”都比较注意,姐妹们都想要知道一些“用java实现单链表”的相关知识。那么小编同时在网上搜集了一些关于“用java实现单链表””的相关资讯,希望朋友们能喜欢,小伙伴们快快来了解一下吧!

C\C++的数据和代码块存储到内存,都有一个地址对应,对于变量,我们显式使用其值,需要显式使用其地址时,用指针变量。

指针变量有“己”和“他”的区隔,包括己型、己址、己值,他型、也址、他值,“己”和“他”都可以做左值,带来便利的同时,也带来了复杂性和安全问题。

多个指针指向同一块栈内存还好说,只存在多个指针可以修改值的问题,不存在内存释放的问题,因为栈内存可以自动释放。如果多个指针指向一块堆内存,就会存在一个所有权的问题,在手动释放时,就会存在一个指针释放,可能另一个指向相同堆内存的指针还在使用的问题。

C++最初只是“C with class”,自然也是包含了指针变量这一语法机制。考虑到指针自身做左值的安全性,以及解引用写起来了的麻烦,C++引入了引用类型,设计引用变量为一个实现了自动解引用的具有常量性质的指针(常量除了要求不能用做左值外,还要求初始化)。

Java作为试图改良C++的编程语言,一定程度上摒弃了C++的复杂性,其中就包括指针,虽然也有引用类型,但是一个与C++不同的引用类型,Java的引用也是一个特殊的指针(显式的地址使用),但不是一个常量,可以用做左值,没有C++考虑让引用作为常量的原因(安全问题)的问题吗,没有,因为其有自动GC(Garbage collection 垃圾回收)功能,且所有对象都是通过一个引用变量来操作,对象本身生存在堆内存上,引用本身生存在栈内存上,由GC根据对象引用的情况来确定对象回收的时间。

JAVA中的对象类型本质上应该叫做对象指针类型。那么传统的对象类型呢?在JAVA里已经不见了踪影!既然没有了传统的对象类型,那么对象引用变量前面的*也就可以不要了。对象引用变量也就可以简称为对象变量了,反正也不会和其它概念混淆! 所有的对象变量都是引用,没有非引用的对象变量,想不用引用都不行,这就是指针的泛化和强化。 不叫指针了,就叫对象变量,这就是概念上的淡化和弱化。 没有了指针的加减运算,也没有了解引用*、对象指针成员引用->等运算符,这是对指针的简单化。

1 指针变量对普通变量和指针变量的副作用

#include <stdio.h>  // 指针的副作用要解引用做左值,// 要对主调函数的一个一级指针产生副作用,被调函数对应的形参必须是一个二级指针#include <malloc.h>void test(int **pp,int *p,int n)     // pp对一个一级指针产生副作用,p对一个变量产生副作用,n传址{    *pp = (int *)malloc(sizeof(int));    **pp = n*10;    *p  =  ++n;    p   = (int *)malloc(sizeof(int));// 指针产生副作用要求解引用做左值,否则是自身的操作,不会产生负作用    *p  = n*100;                     // 此时的解引用不会影响主调函数的值,因为其指针有了另指向                     printf("%d\n",*p);  // 1200}int main(){    int a = 11;    int *p = &a;    test(&p,&a,a);    printf("%d\n",a);   // 12    printf("%d\n",*p);  // 110    getchar();}
2 数组指针用作函数参数

在C语言中,数组是一个特殊指针,是一个具有常量性质,而下标索引也只不过是指针算术运算的语法糖。

#include <stdio.h>#include <malloc.h>void test(int (*ap)[4],int n){         *(*(ap+2)+3) = 23; // 产生副作用也是解引用机制,解引用也就是ap指向的目标对象    //ap是一个指针,指向类型是int[4],所以需要二次解引用产生别作用    // ap[2][3] = 23;   // 下标操作是上述写法的语法糖    ap = (int(*)[4])malloc(sizeof(int)*4*n); // ap自身(己型、己值)做左值,没有副作用    int arr[3][4] = {0};    ap = arr;    ap[2][3] = 46;   }int main(){    int arr[3][4] = {0};    int n = sizeof arr / sizeof *arr;    test(arr,n);    printf("%d\n",arr[2][3]); // 23    getchar();}
3 结构体和结构体指针用作函数参数
#include <stdio.h> // 结构体和结构体指针做函数参数typedef struct contact{    char * name;    char * tel;}ct,*pct;void test(ct c,pct p){    c.name = "ABC";    // 结构体传值不存在副作用     (*p).name = "DEF"; // 解引用对成员产生副作用    // p->name = "DEF";// 上述写法的语法糖    ct d = {"xyz","789"};    p = &d;  // 指针本身做左值,不会对指针目标对象产生副作用    p->name = "JPG";}int main(){    ct c = {"abc","123"};    ct d = {"def","456"};    test(c,&d);    printf("%s\n",c.name);  // abc    printf("%s\n",d.name);  // DEF    getchar();}
4 C++引用相对于指针,使用更简洁
#include <stdio.h>// C++引用相对于指针,使用更简洁void swap(int *a,int *b){    int t = *a;    *a = *b;    *b = t;}void swap(int &a,int &b){    int t = a;    a = b;    b = t;}int main(){    int a=3,b=4;    swap(&a,&b);    swap(a,b);    getchar();}
5 C++实现单链表

结构体可以包含一个自指向的指针变量,因为对于一个数据类型来说,只需要类型确定,需要的内存空间确定。对于自指向的结构体来说,其自指向指针的类型是确定的,内存大小也是确定的(因为所有指针大小都是一个字长对应的字节),所以结构体类型本身也是确定的。

#include <iostream>using namespace std;struct node					//定义结点结构类型{    char data;				//用于存放字符数据    node *next;				//用于指向下一个结点(后继结点)};// …………创建…………node * Create(){    node *head=NULL;		//表头指针,一开始没有任何结点,所以为NULL    node *pEnd=head;		//表尾指针,一开始没有任何结点,所以指向表头    node *pS;				//创建新结点时使用的指针    char temp	;			//用于存放从键盘输入的字符    cout<<"请输入字符串,以#结尾:" <<endl;    do						//循环至少运行一次    {        cin>>temp;        if(temp!='#')			//如果输入的字符不是#,则建立新结点        {            pS=new node;		//创建新结点            pS->data=temp;		//新结点的数据为temp            pS->next=NULL;		//新结点将成为表尾,所以next为NULL            if(head==NULL)	//如果链表还没有任何结点存在                head=pS;		//则表头指针指向这个新结点            else				//否则                pEnd->next=pS;	//把这个新结点连接在表尾            pEnd=pS;			//这个新结点成为了新的表尾        }    }while(temp!='#');			//一旦输入了#,则跳出循环    return head;				//返回表头指针}//…………遍历…………void Showlist(node *head){    node *pRead=head;			//访问指针一开始指向表头    cout<<"链表中的数据为:" <<endl;    while(pRead!=NULL)			//当访问指针存在时(即没有达到表尾之后)    {        cout<<pRead->data;		//输出当前访问结点的数据        pRead=pRead->next;		//访问指针向后移动(指针偏移)    }    cout<<endl;}//…………查找…………node * Search(node *head,char keyWord)	//返回结点的指针{    node *pRead=head;    while(pRead!=NULL)		//采用与遍历类似的方法,当访问指针没有到达表尾之后    {        if(pRead->data==keyWord)	//如果当前结点的数据和查找的数据相符        {            return pRead;			//则返回当前结点的指针        }        pRead=pRead->next;        //数据不匹配,pRead指针向后移动,准备查找下一个结点    }    return NULL;			//所有的结点都不匹配,返回NULL}// …………插入…………void Insert(node * &head,char keyWord,char newdata)//keyWord是查找的字符(确定插入的位置){    node *newnode=new node;				//新建结点    newnode->data=newdata;				//newdata是新结点的数据    node *pGuard=Search(head,keyWord);	//pGuard是插入位置前的结点指针    if(head==NULL || pGuard==NULL)		//如果链表没有结点或找不到关键字结点    {									//则插入表头位置        newnode->next=head;				//先连        head=newnode;					//后断    }    else								//否则    {									//插入在pGuard之后        newnode->next=pGuard->next;		//先连        pGuard->next=newnode;			//后断    }}// …………节点删除…………void Delete(node * &head,char keyWord)		//可能要操作表头指针,所以head是引用{    if(head!=NULL)							//如果链表没有结点,就直接输出提示    {        node *p;        node *pGuard=head;					//初始化pGuard指针        if(head->data==keyWord)			//如果头结点数据符合关键字        {            p=head;							//头结点是待删除结点            head=head->next;				//先连            delete p;						//后断            cout<<"被删除的结点是" <<keyWord<<endl;            return;							//结束函数运行        }        else								//否则        {            while(pGuard->next!=NULL)		//当pGuard没有达到表尾            {                if(pGuard->next->data==keyWord)                    //如果pGuard后继结点数据符合关键字                {                    p=pGuard->next;			//pGuard后继结点是待删除结点                    pGuard->next=p->next;	//先连                    delete p;				//后断                    cout<<"被删除的结点是" <<keyWord<<endl;                    return;					//结束函数运行                }                pGuard=pGuard->next;		//pGuard指针向后移动            }        }    }    cout<<"The keyword node is not found or the link list is empty!" <<endl;}// …………链表删除…………void Destroy(node * &head){    node *p;    while(head!=NULL)			//当还有头结点存在时    {        p=head;					//头结点是待删除结点        head=head->next;		//先连        delete p;				//后断    }    cout<<"The link list has been deleted!" <<endl;}// …………主函数…………int main(){    node *head=NULL;    head=Create();    Showlist(head);    cout<<"在a的位置插入o:"<<endl;    Insert(head,'a','o');    Showlist(head);    cout<<"插除内容是m的节点:"<<endl;    Delete(head,'m');    Showlist(head);    Destroy(head);    Showlist(head);    cin.get();    cin.get();    return 0;}
6 Java 引用做函数参数

Java总体可以区分为三类数据类型,基本数据类型(8种,boolean、char、byte、short、int、long、float、double),对对象的引用,引用指向的对象(包括数组)。

基本数据类型可以包装为对象。

基本数据类型和引用类型生存在栈内存中,引用指向的对象只能保存在堆内存中。

6.1 数组做函数参数的副作用

public class Main{    public static void test(int[][] arr)    {        arr[2][3] = 23;     // arr对其引用对象(数组)元素的操作产生副作用        arr = new int[3][4]; // arr传的是引用本身,可以引用一个新数组        arr[2][3] = 46;        System.out.println(arr[2][3]); // 46    }	public static void main(String[] args) {	    int[][] arr = new int[3][4];   // 默认初始化每一个元素为0	    test(arr);		System.out.println(arr[2][3]); // 23	}}

6.2 对象做函数参数的副作用

class Contact{    public String name;    public String tel;    Contact(String name,String tel){        this.name = name;        this.tel = tel;    }}public class Main{    public static void test(Contact c)    {        c.name = "ABC";             // c对其引用对象元素的操作产生副作用        c = new Contact("def","456"); // c本身是值传递,可以指向新的相同类型的对象        c.name = "DEF";        System.out.println(c.name); // DEF    }	public static void main(String[] args) {	    Contact c = new Contact("abc","123");	    test(c);		System.out.println(c.name); // ABC	}}

C指针做函数实现址传递时,传递的也是址的值,指针本身做左值不会产生副作用,指针解引用操作的是指针指向的目标对象,才会产生副作用。

Java的引用作为参数传递时,传递的也是引用本身的值,操作本身同样不会产生副作用,但当引用的元素或成员做左值时,操作的则是引用指向的堆内存中保存的数据,自然会产生副作用。

Java的方法参数只有两种类型,基本类型和指向对象的引用类型,两者都是值传递,但引用类型传递后在函数体内用作左值时可以是引用本身或引用对象的元素或成员,引用本身做左值不会对主调函数(方法)的目标对象产生副作用,但对引用对象引用其成员或元素时,操作的就不是其本身,而是其引用所指向的目标对象了,自然会产生副作用。

7 Java实现单链表

Java引用作为语法形式上泛化、强化,概念上淡化和弱化,操作上简单化的特殊指针,不像C++的引用是一个常量,Java的引用是一个变量,可以做左值,引用对象时,也可以自引用,自然,也可以实现对象之间的联系,用来实现链式存储。

import java.util.Scanner;import java.util.Random;class Node{    private Node next;    private int  value;        public Node ( int val ){        value = val;        next = null;    }        public int  getValue() { return value; }    public Node getNext()  { return next; }        public void setValue( int val ) { value = val; }    public void setNext( Node nxt ) { next = nxt; }        public String toString() { return value + ", "; }}class LinkedList{    private Node headPtr = null;        // The constructor creates an empty list    public LinkedList()  {        headPtr = null;    }        // Determine if the List is empty    public boolean isEmpty(){        return headPtr == null;        // future work    }        // Insert one Node containing data at the head    // of the list.  This will be explained in a few pages.    public void insertFirst( int data ){        Node newFirst = new Node( data );        newFirst.setNext( headPtr );        headPtr = newFirst;    }    public void deleteFirst(){        if( headPtr != null )        {            headPtr = headPtr.getNext();  // headPtr now points at second node, or            // is null if there was no second node.        }    }    // Traverse the list, printing each node    public void traverse() {        Node p = headPtr;        while( p != null )        {            System.out.print( p );            p = p.getNext();        }    }}public class Main{    public static void main( String[] args )    {        LinkedList list = new LinkedList();        Scanner scan = new Scanner( System.in );        Random rand = new Random();                System.out.print("How many nodes? ");        int numNodes = scan.nextInt();        for( int j = 0; j<numNodes; j++ )        {            list.insertFirst( rand.nextInt(1001) );        }        list.traverse();    }}

ref

剖析C/C++指针、C++引用、Java引用的联系与区别-今日头条

-End-

标签: #用java实现单链表