AcWing C++语法笔记第七节类结构体指针引用

10 篇文章 1 订阅
这篇博客详细介绍了C++中的类与结构体的定义与使用,包括构造函数的概念。同时,深入探讨了指针的基础知识,如指针与地址的关系、数组与指针的交互。此外,还讨论了引用作为变量别名的特性。最后,文章讲解了链表的基本操作,如节点的添加、删除、合并和反转,并提供了相关代码示例。
摘要由CSDN通过智能技术生成

1、该系列为ACWing中c++语法课,已购买正版,课程作者为yxc(请大家支持正版)。
2、为啥写在这儿,问就是oneNote的内存不够了QAQ

一、 类与结构体

1.1 类的定义

类可以将变量、数组和函数完美地打包在一起。

class Person
{
    private:
        int age, height;
        double money;
		int get_height()
		{
			return height();
		}
		
    public:
        string name;

        void say()
        {
            cout << "I'm " << name << endl;
        }

        int get_age() //函数在类里面可以调用age
        {
            return age;
        }

        void add_money(double x)
        {
            money += x;
        }
        
	private:
        string books[100];
};

注意:定义类后面必须有分号;

类中的变量和函数被统一称为类的成员变量。

private后面的内容是私有成员变量,在类的外部不能访问,只能在类里面调用;
public后面的内容是公有成员变量,在类的外部可以访问。
privatepublic可以有多个;

1.2 类的使用

将定义的类作为一种特殊类型;

#include <iostream>

using namespace std;

const int N = 1000010;

class Person
{
    private:
        int age, height;
        double money;
        string books[100];

    public:
        string name;

        void say()
        {
            cout << "I'm " << name << endl;
        }

        int set_age(int a)
        {
            age = a;
        }

        int get_age()
        {
            return age;
        }

        void add_money(double x)
        {
            money += x;
        }
} person_a, person_b, persons[100]; 定义类的第二种方式

int main()
{
    Person c;

    c.name = "yxc";      // 正确!访问公有变量
    c.age = 18;          // 错误!访问私有变量
    c.set_age(18);       // 正确!set_age()是共有成员变量
    c.add_money(10000);  // 正确!访问公有变量

    c.say();
    cout << c.get_age() << endl; // 正确!访问公有变量
	
	Person persons[10000];
    return 0;
}

在定义时可以直接在{}外声明变量person_a(或数组)

结构体和类的作用是一样的。不同点在于类默认是private,结构体默认是public(如果不写privatepublic)。

一般把函数较少的,短的,和数据相关的定义为结构体,而把复杂抽象的,含义比较混乱的打包为class。

struct Person
{
    private:
        int age, height;
        double money;
        string books[100];

    public:
        string name;

        void say()
        {
            cout << "I'm " << name << endl;
        }

        int set_age(int a)
        {
            age = a;
        }

        int get_age()
        {
            return age;
        }

        void add_money(double x)
        {
            money += x;
        }
} person_a, person_b, persons[100];

1.3 定义构造函数

构造函数是没有类型的,名字和结构体的名字完全相同。如果有构造函数,我们可以在构造一个变量时赋初值。

构造函数里面是可以写参数的。

可以在函数内部将Person几个变量去赋值。

如果缺少Person () {}的无参构造函数,不给参数再构造(Person p)会报错。如果给出其他构造函数,Person () {}不会默认给出。

#include <iostream>

using namespace std;

struct Person
{
    int age, height;
    double money;
    
    Person () {}
    Person(int _age, int _height, double _money)
    {
        age = _age;
        height = _height;
        money = _money;
    }
};

int main()
{
    Person p(18, 180, 1000);
    // 18岁,180身高,1000块
    return 0;
}

给定参数初始化结构体,会顺次赋值给变量。

构造函数的特殊写法:

  Person(int _age, int _height, double _money) : age(_age), height(_height), money(_money){}

二、指针和引用

2.1 基本知识

前置知识参考:系统内存
8G内存表示,每个程序在运行时默认占有8GB的内存空间(内存数组)。程序用到的所有空间都开辟在数组里面。

一般将上面用到的连续空间成为栈,下面用到的连续空间称为堆。栈从上往下开辟空间,堆从下往上开辟空间。

函数调用在栈空间操作,局部变量在栈空间。静态变量和数组在堆空间中。
在这里插入图片描述

2.2 指针

指针地址和原值的关系:指针指向存放变量的值的地址(虚拟地址)。

每次运行地址值可能都是不一样的。

例如:char c='a'c变量的地址相当于内存的数组下标。
在这里插入图片描述

取变量的地址的方法是,加取地址符&

char c='a', d;
cout << (void*)&c << endl;
cout << (void*)&d << endl;

一般情况下(有时C++的编译器对代码会优化),连续定义的两个变量,地址也是连续的。

定义一个指针,可以通过指针获取和修改变量的值,类似于通过数组下标修改数组的值

#include <iostream>

using namespace std;

int main()
{
    int a = 10;
    int *p = &a; //或 int* p = &a;
    p是int类型变量的指针,赋值等于a的地址,这里*的意思是定义的变量p是指针类型。
    p此时存的是a的地址。
	
	cout << p << endl;
	cout << p+1 << endl; 输出下一个变量的地址;
	
    *p += 5;
    通过地址取得a的值(并让a加5),这里*的意思是求p的值。
    cout << a << endl;
	
    return 0;
}

在这里插入图片描述
p虽然存的是地址,但p本身也是一个变量。存p变量也有一个地址(指针也有地址)。

int** q = &p;
int*** o = &q;
cout << q << endl;

2.3 数组

数组名是一种特殊的指针(数组指针),存的是数组的开始地址。指针可以做运算:

#include <iostream>

using namespace std;

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    变量a本身就是一个指针,也是a[0]的地址。

    for (int i = 0; i < 5; i ++ )
        cout << (void*)&a[i] << endl; //输出a[i]的地址;
	
	int *p = a; 			
	cout << *(p+2) << endl; //输出a[2]的值;
	cout << *(a+2) << endl; //输出a[2]的值;
	
	scanf("%d", &a[1]); 输入修改a[1]的值;
	scanf("%d", a+1 );  输入修改a[1]的值;
	cout << a[1] << endl;
	
	// long long类型的数组
	long long b[5] = {1,2,3,4,5};
	cout << (void*) b << endl;    
	强转为指针,输出数组b或b[0]的地址;
	//输出b[i]的地址;
	for (int i = 0; i < 5; i ++ )
		cout << (void*)&b[i] << endl; 

    return 0;
}

在这里插入图片描述
数组中每个值,隔了4个字节,是因为是int型变量,每个变量包含4个字节。
在这里插入图片描述
数组不论在栈空间还是堆空间中,地址都是连续的(支持随机索引)。

数组的变量名不止存首地址,也会偷偷存数组长度(使用sizeof函数时,知道数组在什么地方截止)。

字符串数组,若直接输出数组名,则会输出字符串本身。

char c; 
cout << (void*)&c << endl;
输出字符变量的地址;

char a[5] = {1,2,3,4,5};
cout << a << endl;    		  
直接输出了字符串的值(1,2,3..对应的ASCII码);
cout << (void*) a << endl;    
强转为指针,输出字符串的地址;

输出a[i]的地址;
for (int i = 0; i < 5; i ++ )
	cout << (void*)&a[i] << endl; 

用指针做运算:

#include <iostream>

using namespace std;

int main()
{
    int a[5] = {1, 2, 3, 4, 5};
    int *p = &a[0], *q = &a[2];
	
	cout << q-p << endl; 
	结果是2,做的不是数值的减法,是地址的;
   
    return 0;

2.4 引用

引用和指针类似,相当于给变量起了个别名。

#include <iostream>

using namespace std;

int main()
{
    int a = 10;
    int &p = a;简化写法
    //int* p = &a;

    p += 5;
    cout << a << endl;

    return 0;
}

int *p = &a;int &p = a;的区别在于:
int* p = &a;是c语言的写法,即定义了一个指针pa的地址;
int& p = a;是引用(别名),即定义一个变量ppa存到一个地址上,改变a的值,那么p的值也会变。
引用

三. 链表

3.1 链表的定义

#include <iostream>

using namespace std;

struct Node
{
    int val; // 值
    Node* next; // next指针(但不能定义Node类型的变量)

	Node(int _val) : val(_val), next(NULL){}
} ;

int main()
{
	Node node = Node(1); //定义一个Node类型的变量,值为1;
	Node* p = &node;    //一般习惯用指针来描述;

	//合为一步,生成结构体并放到指针里。
	//定义一个Node类型的变量并通过构造函数初始化为1,通过new返回该变量指针(地址),然后赋值给Node* 类型的变量。
	Node* p = new Node(1);
	auto q = new Node(2);  //自动判断类型
	
	p->next = p; //next指针指向自己;
	p->next = q;
	
	//头结点
	Node* head = p;
	
	//遍历链表
	for (Node* i= head; i != NULL ; i = i->next)//判断条件可以写 (i)/( i != NULL)/( i != 0)
		cout << i->val << endl;
	
	
	//在链表中添加一个节点,一般习惯在最开始加(最后节点的位置需要遍历)
	Node* u = new Node(3);
	u->next = head;
	head = u;

	//删除节点
	head ->next = head->next->next;
}

不能再结构体内部(Node)定义自身类型的变量,但可以定义指针。

调用结构体或类里面的某个成员变量时,如果是变量调用那么:

Node a = Node(1);
a.next, a.val;

如果调用成员变量的是地址,那么用->

Node* p = new Node(1);
p->next = p; //指向自己
p->val;

链表结构如下:

头结点一般存到head变量中(算法中说头结点一般指第一个节点的地址,而不是值)。
在这里插入图片描述
链表的添加
链表的添加

链表的删除(指在遍历时遍历不到这个点)
链表的删除

#include <iostream>

using namespace std;

struct Node
{
    int val;
    Node* next;
} *head;

int main()
{
    for (int i = 1; i <= 5; i ++ )
    {
        Node* p = new Node();
        p->val = i;
        p->next = head;
        head = p;
    }
    
	//遍历链表
    for (Node* p = head; p; p = p->next)
        cout << p->val << ' ';
    cout << endl;

    return 0;
}

3.2 在O(1)时间删除链表结点

给定单向链表的一个节点指针,定义一个函数在O(1)时间删除该结点。

假设链表一定存在,并且该节点一定不是尾节点。

数据范围:链表长度 [1,500]。

输入:链表 1->4->6->8
删掉节点:第2个节点即6(头节点为第0个节点)

输出:新链表 1->4->8

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    void deleteNode(ListNode* node) {
        node -> val = node -> next->val; //伪装成下一个点
        node -> next = node ->next->next; //将下一个点删掉
    }
};

可以直接修改(复制)结构体的值的方法,方法二:

class Solution {
public:
    void deleteNode(ListNode* node) {
        *(node) = *(node->next);
    }
};

3.3 合并两个排序的链表

输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。

数据范围:链表长度 [0,500]。

输入:1->3->5 , 2->4->5

输出:1->2->3->4->5->5

class Solution {
public:
    ListNode* merge(ListNode* l1, ListNode* l2) {
        ListNode *dummy = new ListNode(0);
        ListNode *cur = dummy;
        while (l1 != NULL && l2 != NULL) {
            if (l1 -> val < l2 -> val) {
                cur -> next = l1;
                l1 = l1 -> next;
            }
            else {
                cur -> next = l2;
                l2 = l2 -> next;
            }
            cur = cur -> next;
        }
        cur -> next = (l1 != NULL ? l1 : l2);
        return dummy -> next;
    }
};

3.4 反转链表

包含IO:
  // 迭代做法
  // Iterative C++ program to reverse 
  // a linked list 
  #include <iostream> 
  using namespace std; 

  /* Link list node */
  struct Node { 
      int data; 
      struct Node* next; 
      Node(int data) 
      { 
          this->data = data; 
          next = NULL; 
      } 
  }; 

  struct LinkedList { 
      Node* head; 
      LinkedList() 
      { 
          head = NULL; 
      } 

      /* Function to reverse the linked list */
      void reverse() 
      { 
          // Initialize current, previous and 
          // next pointers 
          Node* current = head; 
          Node *prev = NULL, *next = NULL; 

          while (current != NULL) { 
              // Store next 
              next = current->next; 

              // Reverse current node's pointer 
              current->next = prev; 

              // Move pointers one position ahead. 
              prev = current; 
              current = next; 
          } 
          head = prev; 
      } 

      /* Function to print linked list */
      void print() 
      { 
          struct Node* temp = head; 
          while (temp != NULL) { 
              cout << temp->data << " "; 
              temp = temp->next; 
          } 
      } 

      void push(int data) 
      { 
          Node* temp = new Node(data); 
          temp->next = head; 
          head = temp; 
      } 
  }; 

  /* Driver program to test above function*/
  int main() 
  { 
      /* Start with the empty list */
      LinkedList ll; 
      ll.push(20); 
      ll.push(4); 
      ll.push(15); 
      ll.push(85); 

      cout << "Given linked list\n"; 
      ll.print(); 

      ll.reverse(); 

      cout << "\nReversed Linked list \n"; 
      ll.print(); 
      return 0; 
  } 

3.5 空节点的表示方法

c++中空节点有三种写法:0NULLnullptr

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值