2022年10月

Java实现聊天室功能

前提

1. UDP(用户数据报协议)

使用UDP协议时,在客户和服务器程序都可以放置一个DatagramSocket(数据报套接字),DatagramSocket 用于收发数据包,而DaragramPacket包含了具体的信息。准备接收一个数据报时,只需提供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过DatagramSocket,作为信息起源地的因特网地址以及端口编号会自动得到

2. IntAddress类

用于描述和包装一个Internet IP 地址,通过三个方法返回 InetAddress 实例

3. Swing类 和 AWT

构建图形界面

 

服务器端

1. 发送/接收消息

创建 DatagramSocket 对象用于打开指定端口并监听,然后用创建一个 DatagramPacket,利用 DatagramSocket 中的 receive(ds)方法接收数据并放到刚创建的 DatagramPacket 对象中

2. 客户端和服务器端聊天

编写一个简单的聊天图形界面,并在构造函数中用已经创建好的DatagramSocket对象打开特点的端口监听,并用Thread开启Runnable线程。重写run()函数,将接受数据的receive函数写在这里,使程序在任何时候都能够接收客户端发送的数据, 注意数据中包含客户端的IP地址,将它保存起来。另外加一个按钮实现服务器端发送数据,过程和上述客户端发送数据差不多,区别在于上述客户端和服务器端已经connect而现在没有,所以创建DatagramPacket对象使需要在后面加上已经保存的客户端IP地址,然后用send发送数据

3. 客户端和客户端聊天

服务器端不需要知道有多少客户端要连接,所以就不需要多个线程负责接收客户端连接。服务器端不知道客户端什么时候需要连接,所以就需要开启一个线程来接受客户端发送的消息,注意只需要一个线程即可。服务器端收到消息后,需要发给各个客户端,这就需要发送客户端的IP地址,我们知道用receive()方法可以接受数据,DatagramPacket中就包含了IP地址,由于是多个客户端就需要一个集合专门保存各个客户端的IP地址

代码

/**
 *@author big_fw
 *@version 1.0
 */
package group;
 
import java.net.DatagramPacket;//实现无连接分组传送服务。 仅基于该数据包中包含的信息,每个消息从一台机器路由到另一台机器
import java.net.DatagramSocket;//用于发送和接收数据报数据包的套接字
import java.net.SocketAddress;
import java.util.ArrayList;
 
public class gServer implements Runnable{
	
	private DatagramSocket DS;  
	private int Port = 9998;
	
	private ArrayList<SocketAddress> clients = new ArrayList<SocketAddress>(); //保存客户端IP地址
	
	public gServer() throws Exception{		
		try {			
			DS = new DatagramSocket(Port);
			new Thread(this).start();	
		} catch (Exception ex) {
		} 
	}	
	public void run(){
		try{
			while(true){			
				byte[] data = new byte[255];
				DatagramPacket DP = new DatagramPacket(data,data.length);				
				DS.receive(DP);
				
				SocketAddress clientip = DP.getSocketAddress(); 
				
				if(!clients.contains(clientip)){
					clients.add(clientip);
				}
				this.sendAll(DP);
			}		
		}catch(Exception ex){			
		}		
	}	
	private void sendAll(DatagramPacket dp) throws Exception {
		for(SocketAddress sa : clients){
			DatagramPacket dd = new DatagramPacket(dp.getData(),dp.getLength(),sa);				
			DS.send(dd);				
		}
	}
	public static void main(String[] args) throws Exception{
		new gServer();
	}
}

 

客户端

1. 发送/接收消息

创建DatagramSocket对象,创建好IP地址和端口号后,利用DatagramSocket中的connect(ip,port)方法和服务端建立连接,然后利用DatagramSocket中的send(dp)方法发送早已准备好的数据。

2. 客户端和服务器端

客户端和服务器端都差不多,区别在于,客户端需要在构造函数中先和服务器端建立连接,给服务器端发送一个数据包,表示已经建立连接(其实是告诉服务器端自己的IP地址)然后再打开线程。在run()函数中不用保存服务器地址。在发送消息按钮处不需要给DatagramPacket对象加IP地址,因为客户端早已和服务器端建立连接

3. 客户端和客户端

因为是多个客户端之间进行聊天,所以就需要一个name来区别每一个客户端,打开客户端需要输入昵称。连接服务器、发送消息、接收服务器传来的消息,这些操作和上述的类似,这里不在介绍。( 彩蛋)在客户端GUI中我实现了用回车发送消息,并清空输入框的效果。

代码

客户端1

 

package group;

import java.awt.BorderLayout; //边界布局设置了一个容器,安排和调整其组件,以适应五个区域:北,南,东,西和中心
import java.awt.event.ActionEvent;//指示组件定义的动作发生的语义事件,当事件发生时,实现ActionListener接口的对象获得此ActionEvent 
import java.awt.event.ActionListener;//用于接收动作事件的侦听器界面
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.swing.JFrame;
import javax.swing.JOptionPane;//可以轻松地弹出一个标准对话框,提示用户获取值或通知他们某些东西
import javax.swing.JTextArea;//显示纯文本的多行区域
import javax.swing.JTextField;//一个轻量级组件,允许编辑单行文本

public class gClient extends JFrame implements Runnable,ActionListener {

    private JTextField field = new JTextField();
    private JTextArea area = new JTextArea("聊天内容:\n");

    private String name = null;

    private int Port = 9998;
    private DatagramSocket DS;

    public gClient(){

        this.setTitle("客户端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(area, BorderLayout.CENTER); //private JTextArea area = new JTextArea("聊天内容:\n");
        this.add(field, BorderLayout.SOUTH); //private JTextField field = new JTextField();
        field.addActionListener(this);
        this.setSize(220, 290);
        this.setVisible(true);

        name = JOptionPane.showInputDialog("输入昵称");  //标准对话框

        try {
            DS = new DatagramSocket();
            InetAddress address = InetAddress.getByName("Localhost");
            DS.connect(address,Port);

            String str = name + "登录!";
            byte[] data = str.getBytes();
            DatagramPacket DP = new DatagramPacket(data,data.length);

            DS.send(DP);
            new Thread(this).start();

        } catch (Exception e) {
        }
    }
    public void run(){
        try{
            while(true){
                byte[] data = new byte[255];
                DatagramPacket DP = new DatagramPacket(data,data.length);
                DS.receive(DP);
                String str = new String(DP.getData(),0,DP.getLength());
                area.append(str + '\n');
            }
        }catch(Exception ex){
        }
    }
    public void actionPerformed(ActionEvent e){
        try{
            String str = name + "说:" + field.getText();
            byte[] dd = str.getBytes();
            DatagramPacket Data = new DatagramPacket(dd,dd.length);
            DS.send(Data);
            field.setText("");
        }catch(Exception ex){
        }
    }
    public static void main(String[] args){
        new gClient();
    }
}

 

客户端2

 

package group;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class gClient2 extends JFrame implements Runnable,ActionListener {

    private JTextField field = new JTextField();
    private JTextArea area = new JTextArea("聊天内容:\n");

    private String name = null;

    private int Port = 9998;
    private DatagramSocket DS;

    public gClient2(){

        this.setTitle("客户端");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.add(area, BorderLayout.CENTER);
        this.add(field, BorderLayout.SOUTH);
        field.addActionListener(this);
        this.setSize(220, 290);
        this.setVisible(true);

        name = JOptionPane.showInputDialog("输入昵称");

        try {
            DS = new DatagramSocket();
            InetAddress address = InetAddress.getByName("Localhost");
            DS.connect(address,Port);

            String str = name + "登录!";
            byte[] data = str.getBytes();
            DatagramPacket DP = new DatagramPacket(data,data.length);

            DS.send(DP);
            new Thread(this).start();

        } catch (Exception e) {
        }
    }
    public void run(){
        try{
            while(true){
                byte[] data = new byte[255];
                DatagramPacket DP = new DatagramPacket(data,data.length);
                DS.receive(DP);
                String str = new String(DP.getData(),0,DP.getLength());
                area.append(str + '\n');
            }
        }catch(Exception ex){
        }
    }
    public void actionPerformed(ActionEvent e){
        try{
            String str = name + "说:" + field.getText();
            byte[] dd = str.getBytes();
            DatagramPacket Data = new DatagramPacket(dd,dd.length);
            DS.send(Data);
            field.setText("");
        }catch(Exception ex){
        }
    }
    public static void main(String[] args){
        new group.gClient();
    }
}

 

 

--> Java实现聊天室功能前提1. UDP(用户数据报协议)使用UDP协议时,在客户和服务器程序都可以放置一个DatagramSocket(数据报套接字),DatagramSocket 用于收发数据包,而DaragramPacket包含了具体的信息。准备接收一个数据报时,只需提供一个缓冲区,以便安置接收到的数据。数据包抵达时,通过DatagramSocket,作为信息起源地的因特网地址以及端口编...

顺序栈

表示

#define MAXSIZE 100
typedef struct {
    SElemType *base; //栈底指针(表头端)
    SelemType *top;  //栈顶指针(表尾端)
    int stacksize;   //栈可用最大容量
}SqStack;

 

初始化

Status InitStack(SqStack &S) {
    //构造一个空栈
    S.base = (SElemType*)malloc(MAXSIZE*sizeof*(SElemType));
    if (!S.base) 
        exit (OVERFLOW);  //存储分配失败
    
    S.top = S.base;       //栈顶指针等于栈底指针
    S.stacksize = MAXSIZE;
    return OK;
}

 

判断是否为空

Status StackEmpty(SqStack S) {
    if (S.top == S.base)   //栈为空标志:S.top == S.base
        return TRUE;
    else
        return FALSE;
}

 

清空

Status ClearStack(SqStack &S) {
    if (S.base)
        S.top = S.base;
    return OK;
}

 

销毁

Status Destroy(SqStack &S) {
    if (S.base){
        free S.base;
        S.stacksize = 0;
        S.base = S.top = NULL;
    }
}

 

入栈

Status Push(SqStack &S, SElemType e) {
    if (S.top - S.base == S.stacksize) //栈满
        return ERROR;
    *S.top++ = e;   //*S.top = e; S.top++;
    return Ok;
}

 

出栈

Status Pop(SqStack &S, SElemType &e) {
    if (S.top == S.base)   //栈为空
        return ERROR;
    e = *--S.top;  //--S.top; e = *S.top
    return OK;
}

 

获取栈顶元素值

Status GetTop(SqStack S, SElemType &e) {
    if (S.top == S.base)
        return ERROR;
    e = *(S.top-1);
    return OK;
}

 

 

链栈

表示

  1. 链表的头指针就是栈顶
  2. 不需要头结点
  3. 基本不存在栈满的情况
  4. 空栈相当于头指针指向空
  5. 插入和删除仅在栈顶出执行
typedef struct StackNode {
    SElemType data;
    struct StackNode *next;
}StackNode, *LinkStack;
LinkStack S;

 

初始化

void InitStack(LinkStack &S) {
    S = NULL; //空栈,栈顶指针置空
    return OK;
}

 

判断是否为空

Status StackEmpty(LinkStack S) {
    if (S == NULL)
        return TRUE;
    else
        return FALSE;
}

 

入栈

Status Push(LinkStack &S, SElemType e) {
    StackNode *p;
    p->data = e;    //将新结点数据域置为 e
    p->next = S;    //将新结点插入栈顶
    S = p;          //修改栈顶指针
    return OK;
}

 

出栈

Status Pop(LinkStatck &S, SElemType e) {
    if (S == NULL)
        return ERROR;
    e = S->data;
    p = S;
    S = S->next;
}

 

C语言实现代码

顺序栈

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

#define MAX_SIZE 5    /* 栈最大容量 */
#define Empty 0        /* 空 */
#define Full 1        /* 满 */
#define Avail -1    /* 可用 */

typedef struct sta
{
    int *top;            /* 栈顶指针 */
    int *base;        /* 栈底指针 */
    int stacksize;        /* 栈的最大容量 */
}SqStack;

SqStack InitStack (SqStack p);    // 初始化栈
SqStack Push (SqStack p);        //入栈
SqStack Pop (SqStack p);        // 出栈
void DisplyStack (SqStack p);    // 遍历栈中元素
int StackEmpty (SqStack p);    // 判断栈是否为空
int StackFull (SqStack p);    // 判断栈是否为满

int main()
{
    SqStack p;
    char ch;

    p.stacksize = MAX_SIZE;
    p = InitStack (p);    /* 初始化栈 */

    //入栈
    printf("你想要添加元素吗到栈吗? (Y/N)\n");
    scanf(" %c", &ch);
    while (ch == 'Y' || ch == 'y')
    {
        p = Push (p);    /* 入栈 */
        DisplyStack (p);/* 打印栈中元素 */
        printf("你想要添加元素吗到栈吗?(Y/N)\n");
        scanf(" %c", &ch);
    }

    //出栈
    printf("你想出栈吗?(Y/N)\n");
    scanf(" %c", &ch);
    while (ch == 'Y' || ch == 'y')
    {
        p = Pop (p);
        DisplyStack (p);
        printf("你想出栈吗?(Y/N)\n");
        scanf(" %c", &ch);
    }
    return 0;
}

//初始化
SqStack InitStack (SqStack p)
{
    p.base = (int *)malloc(p.stacksize * sizeof(int));
    if (p.base == NULL)
    {
        printf("初始化栈失败\n");
        exit(0);
    }
    p.top = p.base;
    p.stacksize = MAX_SIZE;

    return p;
}

//入栈
SqStack Push (SqStack p)
{
    int data;
    if (StackFull(p) == Full)
    {
        printf("栈空间已满,无法入栈");
        return p;
    }
    printf("输入数字:");
    scanf("%d", &data);
    *p.top = data;
    p.top++;      //*p.top++ = data

    return p;
}

//出栈
SqStack Pop (SqStack p)
{
    int data;
    if (StackEmpty(p) == Empty)
    {
        printf("栈为空栈,无法出栈 ");
        return p;
    }
   // p.top--;
   // printf("出栈元素为:%d\n", *p.top);
     data = *--p.top;
     printf("出栈元素为:%d\n",data);
    return p;
}

//判断栈是否为空
int StackEmpty (SqStack p)
{
    if (p.top == p.base)
    {
        return Empty;
    }
    else
    {
        return Avail;
    }
}

//判断栈是否为满
int StackFull (SqStack p)
{
    if (p.top - p.base == p.stacksize)
    {
        return Full;
    }
    else
    {
        return Avail;
    }
}

//遍历栈中元素,从栈顶到栈底
void DisplyStack (SqStack p)
{
    if (StackEmpty(p) == Empty)
    {
        printf("栈为空栈,无法遍历\n");
        return;
    }
    printf("栈中元素为:\n");
    printf("顶端      [");
    while (p.top != p.base)
    {
        //p.top--;
        //printf("%d",*p.top);
        printf("%d", *--p.top);
    }
    printf("]       底端\n");
}

 

链栈

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

typedef struct lineStack {
    int data;
    struct StackNode *next;
}StackNode, *LineStack;

LineStack push(LineStack S, int a);  //入栈
LineStack pop(LineStack S);            //出栈

int main() {
    LineStack S = NULL;
    S = push(S, 1);
    S = push(S, 2);
    S = push(S, 3);
    S= push(S, 4);
    S = pop(S);
    S = pop(S);
    S= pop(S);
    S = pop(S);
    S = pop(S);
    return 0;
}


//入栈
LineStack push(LineStack S, int a) {
    //创建存储新元素的节点
    StackNode* line = (LineStack)malloc(sizeof(StackNode*));
    line->data = a;
    //新节点与头节点建立逻辑关系
    line->next = S;
    //更新头指针的指向
    S = line;
    return S;
}

//出栈
LineStack pop(LineStack S) {
    if (S) {
        //声明一个新指针指向栈顶节点
        StackNode* p = S;
        //更新头指针
        S = S->next;
        printf("出栈元素:%d ", p->data);
        if (S) {
            printf("新栈顶元素:%d\n", S->data);
        }
        else {
            printf("栈已空\n");
        }
        free(p);
    }
    else {
        printf("栈内没有元素(空栈)");
        return S;
    }
    return S;
}

 

 

--> 顺序栈表示#define MAXSIZE 100typedef struct { SElemType *base; //栈底指针(表头端) SelemType *top; //栈顶指针(表尾端) int stacksize; //栈可用最大容量}SqStack; 初始化Status InitStack(SqStack &S) { //构造一个空...

单链表操作

  1. Status 是函数的类型,其值是函数结果状态代码

    typedef int Status

  2. //函数结果状态代码

    #define True 1

    #define False 0

    #define OK 1;

    #define ERROR 0

    #define INFEASIBLE -1

    #define OVERFLOW -2




     

1.单链表的定义和表示

typedef struct {
    char name[10];
    char num[20];
    double score;
}Stu;

typedef struct LNode {
    Stu data;
    struct LNode *next;
}LNode,*LinkList;

 

2.初始化链表(带头结点的单链表)

思路:

  1. 生成新结点作头结点,用头指针L指向头结点
  2. 头结点的指针域置空
Status InitList_L(LinkList L)
{
    L = (LinkList)mallloc(sizeof(LNode));
    L->next = NULL;
    return OK;
}

 

3.判断链表是否为空

  1. 空表:链表中无元素(头指针和头结点仍然存在)

    思路:判断头结点指针域是否为空

    Status ListEmpty(LinkList L)
    {
       if(L->netx)
           return 0;
        else
            return 1;
    }
    

 

4.销毁单链表

思路:从头指针开始,依次释放所有结点

Status DestroyList_L(LinkList L)
{
    LNode *p; //或者 LinkList p;
    while(L)
    {
        p=L;
        L=L->next;
        free p;
    }
}

 

5.清空单链表

思路:依次释放所有结点,并为头结点指针域设置为空

Status ClearList(LinkList L)
{
    LNode *p,*q;
    p=L->next;
    while(p)
    {
        q = p->next;
        free p;
        p = q;
    }
    L->next = NULL; //头结点指针域为空
    return OK;
}

 

6.求单链表长度

思路:从首元结点开始,依次计数所有结点

Status ListLength_L(LinkList L)
{
    LNode *p;
    p = L->next; //p指向第一个结点
    int i = 0;
    while(p)  //遍历单链表,统计结点数
    {
        i++;
        p = p->next;
    }
    return i;
}

 

7.取值

Status GetElem_L(LinkList L, int i, ElemType e)
{
    LinkList p = L->next; 
    int j = 1;  //p指向第一个结点,j做计数器,累计当前扫描过的结点数
    while(p && j<i)  //向后扫描,p指向第i个元素或者p为空
    {
        p = p->next;
        j++;
    }
    if (!p || j>i)   //第i个元素不存在
        return ERROR;
    e = p->data;    //取第i个元素
    return Ok;
}

 

8.按值查找

Status LocatElem_L(LinkList L,Elemtype e)
{
    LinkList p = L->next;
    int j = 1;
    while(p && p->data != e)
    {
        p = p->next;  //遍历
        j++;          //位置序号
    }
    if(p)
        return i;
    else 
        return 0;
}

 

9.插入值

思路:

  1. 想要在链表第 i 个元素前面插入新结点,则需要将指针指向第 i-1 个元素,从而将其 next 域中保存的第 i 个元素地址赋值给新结点,实现链接
LinkList p = L;
int j = 0;
while(p && j<i-1)
  1. 代码中创建了指向头结点的指针变量,初始化起点并非首元结点,所以要想在第 i 个位置插入,while 循环次数需为 i-1

    若初始化为首元结点,即

    LinkList p = L->next;
    

    则 while 循环次数为 i-2,从而根据循环次数要求去初始化 j 或改变循环结束的条件

     

Status ListInsert_L(LinkList L, int i, Elemtype e)
{
    LinkList p = L;
    int j = 0;
    while(p && j<i-1)
    {
        p = p->next;
        ++j;
    }
    if(!p || j>i-1)
        return ERROR;
    s = (LinkList)malloc(sizeof(LNode));
    s->data = e;
    s->next = p->next;
    p->next = s;
    return Ok;
}

 

10. 删除第 i 个结点

Status ListDelete_L(LinkList_L, int i, Elemtype e)
{
    LinkList p = L;
    LinkList q;
    int j = 0;
    while(p->next && j<i-1)   //寻找第i个结点,并令p指向其前驱
    {
        p = p->next;
        ++j;
    }
    if(!(p->next) || j>i-1) //删除位置不合理
        return ERROR;
    q = p->next;        //临时保存被删除结点的地址以备释放
    p->next = q->next;  //改变删除结点的前驱结点的指针域
    e = q->data;        //保存删除结点的数据域
    free q;             //释放删除结点的空间
    return OK;
}


 

单链表的创建

1. 头插法(前插法)

思路:

  1. 从一个空表L开始,重复读入数据;
  2. 生成新结点,将读入数据存放到新结点的数据域中
  3. 从最后一个结点开始,依次将各结点插入到链表的前端
void CreatList_H(LinkList L, int n)
{
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;  //建立一个带头结点的单链表
    for(i=n; i>0; --i)
    {
       p = (LinkList)malloc(sizeof(LNode));  //生成新结点p
        scanf(&p->data);                    //获取输入值
        p->next = L->next;                //插入到表头
        L->next = p;
    }
}

 

2. 尾插法(后插法)

思路:

  1. 从一个空表L开始,将新结点逐个插入到链表的尾部,尾指针 r 指向链表的尾结点
  2. 初始时,r 同 L 均指向头结点。每读入一个数据元素则申请一个新结点,将新结点插入到尾结点后,r 指向新结点
void CreatList(LinkList L, int n)
{
    L = (LinkList)malloc(sizeof(LNode));
    L->next = NULL;
    LNode *r;
    r = L;   //尾指针 r 指向头结点
    for(i=0; i<n; i++)
    {
        p = (LinkList)malloc(sizeof(LNode));   //生成新结点,输入元素值
        scanf(&p->data);
        p->next = NULL;
        r->next = p;    //插入到表尾
        r = p;          //r 指向新的尾结点
    }
}

 

循环链表

1.创建

//先创建初始化结点函数
LinkList initlist()
{
    LNode *head = (LinkList)malloc(sizeof(LNode));
    if(head == NULL)
    {
        printf("创建失败,退出程序");
    }
    else
    {
        head->next = NULL;
        return head;
    }
}

//创建循环链表
Status Insert_List(LNode *head)
{
    int data;
    printf("输入插入的元素:");
    scanf("%d",&data);
    
    LinkList node = initlist();
    node->data = data;   //初始化新结点
    
    if(head != NULL)
    {
        LNode *p = head;
        while(p->next != head)  //找到最后一个元素
        {
            p = p->next;
        }
        p->next = node;
        node->next = head;
        return 1;
    }
    else
    {
        printf("头结点已无元素\n");
        return 0;
    }
}

 

2. 带尾指针循环链表合并

LinkList Connect(LinkList Ta, LinkList Tb)
{  //假设Ta,Tb都是非空的单循环链表 
    p = Ta->netx;       //p 存表头结点
    Ta->next = Tb->next->next;  //Tb 表头连接 Ta 表尾
    free Tb->next;         //释放 Tb 表头结点
    Tb->next = p;          //修改指针
    return Yb;
}//时间复杂度O(1)

 

双向链表

1. 创建

//结点创建
typedef struct DuLNode
{
    Elemtype data;           //data
    struct DuLNode *pre;   //前驱 node
    struct DuLNode *next;  //后继 node
}DuLNode,*DuLinkList;

//插入数据
Status ListInsert_DuL(DuLinkList L, int i, ElemType e)
{
    //在带头结点的双链循环线性表L中第i个位置之前插入元素e
    //i的合法值为 1<=i<=表长+1
    if(!(p = GetElemP_DuL(L,i)))  //在L中确定插入的位置
        return ERROR;         //p=NULL,即插入位置不合法
    if(!(s=(DuLinkList)malloc(sizeof(DuLNode))))
        return ERROR;
    s->data = e;
    s->prior = p->prior;    p->prior->next = s;
    s->next = p;            p->prior = s;
    return OK;
}//LinkInsert_DuL

2.删除

Status ListDelete_DuL(DuLinkList L, int i, ElemType e)
{
    //删除带头结点的双链循环线性表L的第i个元素,i的合法值为1<=i<=表长
    if(!(p = GetElemP_DuL(L,i)))   //在L中确定第i个元素的位置指针p
        return ERROR;
    e = p->data;
    p->prior->next = p->next;
    p->next->prior = p->prior;
    free(p);
    return OK;
}ListDelete_DuL

 

--> 单链表操作Status 是函数的类型,其值是函数结果状态代码typedef int Status//函数结果状态代码#define True 1#define False 0#define OK 1;#define ERROR 0#define INFEASIBLE -1#define OVERFLOW -2 1.单链表的定义和表示typedef struct { c...

public class MyThread2 implements Runnable{
	int count = 1, number;

	public MyThread2(int i){
		number = i;
		System.out.println("创建线程"+number);
	}

	public void run(){
		while(true){
			System.out.println("线程"+number+":计数"+count);
			if(++count == 6)
				return;
		}
	}

	public static void main(String[] args) {
		for(int i=0; i<5; i++){
			new Thread(new MyThread2(i+1)).start();
		}
	}
}

结果

创建线程1
创建线程2
创建线程3
线程1:计数1
线程1:计数2
线程2:计数1
线程2:计数2
线程2:计数3
线程3:计数1
线程1:计数3
线程3:计数2
线程3:计数3

 

--> 前提            通过实现接口创建线程的方法通过生成实现java.lang.Runnable接口的类创建多线程。该接口只定义了一个方法run(),所以必须在新类中实现它。但是Runnable接口并没有任何对线程的支持,还必须创建Thread类的实例,这一:点可以通过Thread类的构造方法public Thread( Runnable target)来实现。通过这种方式实现多线程还...

     

前提

主要通过继承java.lang.Thread类,并覆盖Thread类的run()方法完成线程的创建

Thread类是一个具体的类,即不是抽象类,该类封装了线程的行为。要创建线程,需先创建一个Thread类的子类,Thread类中有两个最重要的方法run()和start()

run()方法必须重写,把线程所要的代码加入到这个方法中,也就是线程体

虽然run()方法是线程体,但不能直接调用,而是通过调用start()方法来启动线程。在调用start()的时候,start()方法会首先进行与多线程相关的初始化,然后调用run()方法

start和run方法

start: 用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法run称为线程体,它包含了要执行的这个线程的内容,run方法运行结束,此线程随机终止。


run:run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的

代码

public class MyThread extends Thread{
	//count变量用于统计打印次数并共享变量
	private static int count = 0;
	public MyThread(String name)
	{
		//super 关键字可以在子类的构造方法中显式地调用父类的构造方法
		//访问父类的成员方法和变量
		super(name);
	}
	public static void main(String[] args) {
		//main方法开始
		//实例化线程
		MyThread p = new MyThread("t1");
		p.start();
		//主线程main方法执行一个循环
		for(int i=1; i<4; i++){
			count++;
			//主线程中打印count+"main"变量的值,并换行
			System.out.println("主线程 "+ count +" main");
		}
	}

	public void run(){
		//线程类必须有run()方法
		for(int i=1; i<4; i++){
			count++;
			System.out.println("run方法 "+count+": "+this.getName());
		}
	}
}

运行结果

主线程 1 main
run方法 2: t1
主线程 3 main
run方法 4: t1
主线程 5 main
run方法 6: t1

这段程序用Java虚拟机启动程序后,main方法生成新线程t1,并通过for循环输出变量count的值和线程名称

-->      前提主要通过继承java.lang.Thread类,并覆盖Thread类的run()方法完成线程的创建Thread类是一个具体的类,即不是抽象类,该类封装了线程的行为。要创建线程,需先创建一个Thread类的子类,Thread类中有两个最重要的方法run()和start()run()方法必须重写,把线程所要的代码加入到这个方法中,也就是线程体虽然run...