1、该系列为ACWing中c++语法课,已购买正版,课程作者为yxc(请大家支持正版)。
2、为啥写在这儿,问就是oneNote的内存不够了QAQ
ACwing C++ 语法笔记7类、结构体、指针、引用
一、 类与结构体
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
后面的内容是公有成员变量,在类的外部可以访问。
private
和 public
可以有多个;
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
(如果不写private
和public
)。
一般把函数较少的,短的,和数据相关的定义为结构体,而把复杂抽象的,含义比较混乱的打包为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语言的写法,即定义了一个指针p
是a
的地址;
int& p = a;
是引用(别名),即定义一个变量p
,p
和a
存到一个地址上,改变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++
中空节点有三种写法:0
,NULL
, nullptr
;