课程笔记
课程ppt: 高级程序设计2025春季
这里笔记的顺序是按照单元顺序来写的,只有大纲;
标注的必考和200%必考的更加注意。
第一课:抽象与封装
抽象与封装使得开发者无需关心底层
内部实现不影响外部的使用
例子:用链表封装stack
#include <bits/stdc++.h>
using namespace std;
typedef struct Node
{
int num;
Node *prev;
Node(int num, Node *prev = nullptr) : num(num), prev(prev) {}
} node;
class Stack
{
public:
void push(int num);
void pop();
int get_size();
bool empty();
int& top();
Stack();
// 在pubulic 里面定义的是interface
private:
node *tail;
int size = 0;
// 数据常常标为private
};
void Stack::push(int num)
{
size += 1;
tail = new node(num, tail);
}
void Stack::pop()
{
if (!empty())
{
delete tail;
tail = tail->prev;
size -= 1;
}
}
int Stack::get_size()
{
return size;
}
bool Stack::empty()
{
return size == 0;
}
int& Stack::top(){
return (tail->num);
}
Stack::Stack():tail(nullptr),size(0){}
int main()
{
Stack st;
for(int i=0;i<10;i++){
st.push(i);
}
for(int i=0;i<10;i++){
cout<<st.top()<<" ";
st.pop();
}
cout<<"\n";
for(int i=0;i<10;i++){
st.push(i);
}
for(int i=0;i<10;i++){
st.top()+=1;
cout<<st.top()<<" ";
st.pop();
}
cout<<"\n-------------------\n";
cout<<"size: "<<st.get_size()<<" isEmpty? "<<st.empty();
return 0;
}
第二课:面向对象
细节:class在定义完成之前是不能创建实例的;但是可以创建这个类型的pointer和reference
class A{
public:
A a;//Error! Compiler don't konw how much memory to allocate
A* p;//OK
A& r;//OK
}
下面内容必考,前面不是重点
对象的创建方式(注意不同创建的方式,高频考点)
直接创建 : A a; 内存在stack区,作用域结束后内存自动释放
间接方式创建动态变量:A* p=new A;
//在最后记得要delete p!!delete对应的是p所指的heap上面的空间 //p是栈上的变量,但是p所指的是heap上的一段空间 delete p;
public, private,protected:
- public: 可以在class之外被访问
- private: 在class之外不能访问,不能被derived class继承
- protected:在class之外不能别访问,可以被derived class继承
友元函数可以访问public和protected
对象作为函数参数进行传递,作为函数返回值(必考点,区分下面的区别)!!!必考
void fun(A a){
//...
}
//传入对象,并创建一个临时的A对象
void g(A& a){
//....
}
//传入的是原先对象的引用
Date f1(A& a){
return a;
}
//会创建一个临时变量并返回
Date& f1(A& a){
return a;
}
//只会return 临时的变量!
注意f1
return了原来的对象!!
构造函数和析构函数
构造函数可以有多个,可以重载:
class A{ public: A(); A(int a); A(A& a); } A::A(){ cout<<"Default class constructor\n"; } A::A(int a){ cout<<"This is " }
此外,还可以本地初始化,在类创建的时候,还可以
如果在instance中申请了额外的内存空间,那么必须自定义一个析构函数来销毁这个类的instance
什么时候必须要析构函数下面这个200%必考!!!!!!!!!!!

图中讲str赋值为nullptr是为了更加地安全。也可以没有
内存是怎么分配的?
- stack里面有一个String instance: int 4bytes, char* 4bytes; 8bytes in total;
- 在调用构造函数的时候,在heap上要了5个char位置,存放'a','b','c','d','\n'.
当这个类的instance的作用域消失以后,编译器会自动调用这个类的析构函数;如果没有的话,就会自动创造一个析构函数,但是这个默认的够细函数是不会自己去销毁heap上的内存,需要手动销毁,防止内存泄露!
this指针
每一个类的成员函数其实都会隐藏一个默认的this
指针。面向对象其实是一种思想。
不重要,不是考点。
拷贝构造函数(考点之一)
你必须care拷贝构造函数。
相当于是构造函数一种特殊的重载。
class A{
public:
int data;
A(int data):data(data)
A(A& a):data(a.data)//拷贝构造函数
}
三种情况下会调用这个拷贝构造函数:
- 利用一个instance 创造另一个instance
- 当一个instance作为参数传递给一个函数时(pass by value,会在函数内部调用拷贝构造函数创建一个新的临时对象)
- 当一个instance作为函数返回值时 (注意,和上面一样,不是referece,而是pass by value)
如果没有构造函数,就会生成一段简单的拷贝构造函数。
- 直接将成员里面的变量赋值给被拷贝的对象,是
shallow copy
!
比如,以下面这个200%会考的内容为例:
//例如,在下面的类中没有自定义拷贝构造函数:
class String
{ int len;
char *str;
public:
String(char *s)
{ len = strlen(s);
str = new char[len+1];
strcpy(str,s);
}
~String() { delete []str; len=0; str=NULL; }
};
......
String s1("abcd");
String s2(s1);
- 系统提供的隐式拷贝构造函数将会使得s1和s2的成员指针str指向同一块内存区域!
会带来一系列的内存安全问题
- 如果对一个对象(s1或s2)操作之后修改了这块空间的内容,则另一个对象将会受到影响。如果不是设计者特意所为,这将是一个隐藏的错误。
- 当对象s1和s2消亡时,将会分别去调用它们的析构函数,这会使得同一块内存区域将被归还两次,从而导致程序运行错误。
- 当对象s1和s2中有一个消亡,另一个还没消亡时,则会出现使用已被归还的空间问题!
我们需要自定义拷贝构造函数,进行deep copy
String(String& s){
len=s.len;
str=new char[len+1];
strcpy(str,s.str);//将s.str的内容复制到str中
}
3.2常成员函数
常成员函数
- 可以用const约束函数,使得这个函数不能修改类成员的值:
class A{
int a;
int* b;
public:
void func() const{
a=1;//Error, 不能改;
b=new int(1);//Error不能修改b的值
*b=1;//正确,因为b的值没有改变,只是改变了b所指的地址的值。
}
}
- 常成员只能调用常成员函数

静态数据成员
- 存储在静态存储区,内存为所有类的实例共享。
- 必须在类的内部声明,并且在类的外部初始化:
class A{
public:
static int a;//不能在类的内部赋值
}
\\.....
int A::a=10;//在类的外面赋值
静态成员函数
只能访问到静态成员对象
友元
友元不是类的成员,但是可以访问类的所有成员,包括private和protected。
如:
熟悉下面这个即可
class A{
int a;
public:
friend ostream& operator<<(ostream& os, A& a){
os<<"A("<<a<<")";
return os;
}
friend istream& operator>>(istream& os,A& a){
os>>a.a;
return os;
}
};
3.4类的模块化设计
cpp里面的模块化设计
- 接口:包含被外界使用的类型定义、常量定义以及全局变量和函数的声明。(.h文件)
- 实现:包含本模块中所有的类型、常量、全局变量和函数的定义。(.cpp文件)
4.1Inheritance继承
派生类(derived class),即子类。
基类(base class),即父类。
继承从基类的数量,分为多继承(很少考)
和单继承
继承按照继承的方式可以分为 public继承
private继承(by default)
protected继承
派生类:
- 有子类的所有成员和函数,但是不能访问
private
成员;有,但是不能访问! - 可以override base class的成员
#include<iostream>
using namespace std;
class A{
int a;
public:
int fun(){
cout<<"驿站寄旧衣,七毛一公斤"<<endl;
}
};
class B:public A{
void fun(){
cout<<a;//Error
}
};
class C: public A{
int a;//override
void fun(){
cout<<a;//It's ok!
}
};
- 注意,如果要覆盖的话,就需要用作用域修饰符:
#include<iostream>
using namespace std;
class A{
int a;
public:
void fun(){
cout<<"sdddsds";
}
};
class B:public A{
public:
void fun(){
cout<<"I am a member function of B"<<endl;
}
void f(){
fun();
A::fun();
//output: I am a member function of B
// sdddsds
}
};
派生类的成员对外访问情况 | 百分之一百的考点!!!
考法为程序分析题,给一段代码,找出错误。
从base class继承而来的成员的访问限定情况,和继承的方式有关:

关注ppt上的那个例题。
子类型
派生类就是基类的一个子类型;
- 派生类对象可以直接赋值给基类,其中不属于基类的成员将被忽略。
- 基类的指针可以指向派生类。