图源:文心一言
听课笔记简单整理,供小伙伴们参考,内容包含“🐋5.2 变量的生存期与可见性、🐋5.5 静态成员与静态函数、🐋5.6 友元函数与友元类、🐋5.7 共享数据的保护 / const关键字、🐋5.10 多文件工程”~🥝🥝
- 第1版:听课的记录代码~🧩🧩
编辑:梅头脑🌸
审核:文心一言
目录
🐳课程来源
🐳数据的共享与保护
🐋5.2 变量的生存期与可见性
🐋5.5 静态成员与静态函数
🐋5.6 友元函数与友元类
🐋5.7 共享数据的保护 / const关键字
🐋5.10 多文件工程
🔚结语
🐳课程来源
- 郑莉老师的公开课:🌸C++语言程序设计基础 - 清华大学 - 学堂在线 (xuetangx.com)
🐳数据的共享与保护
🐋5.2 变量的生存期与可见性
⌨️算法代码
#include <iostream>
using namespace std;
int i = 1; // i 为全局变量,具有静态生存期。
void other() {
static int a = 2; // a 和 b 为静态变量,具有全局寿命,局部可见,只第一次进入函数被初始化
static int b;
int c = 10; // c 为局部变量,具有动态生存期,每次进入函数时都初始化
a += 2; i += 32; c += 5;
cout << "---OTHER---\n";
cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
b = a;
}
int main()
{
static int a;
int b = -10;
int c = 0;
cout << "---MAIN---\n";
cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
c += 8; other();
cout << "---MAIN---\n";
cout << "i:" << i << " a:" << a << " b:" << b << " c:" << c << endl;
i += 10; other();
return 0;
}
📇执行结果
代码执行流程 i a b c main 1 0 -10 0 全局变量 main局部静态变量
main局部变量 main局部变量 other 1 + 32 = 33 2 + 2 = 4 0 10 + 5 = 15 全局变量 other局部静态变量 other局部静态变量 other局部变量 main 33 0 -10 0 + 8 = 8 全局变量 main局部静态变量
main局部变量 main局部变量 other 33 + 32 + 10 = 75 4 + 2 = 6 4 (上一轮a值) 10 + 5 = 15 全局变量 other局部静态变量 other局部静态变量 other局部变量
📇相关概念
局部变量、全局变量、静态变量、动态变量 作用域 存储期 存储位置 未显示初始化 局部变量
局部于定义它的函数或代码块。 从变量进入作用域到作用域结束。 通常存储在栈(stack)上。 未定义。 全局变量 整个程序。 从程序开始执行到程序结束。 通常存储在全局/静态存储区。 数据:0;
指针:空。
静态变量 可以是局部的或全局的。 从程序开始执行到程序结束。 通常存储在全局/静态存储区。 数据:0;
指针:空。
动态变量 程序员的显式管理。 程序员的显式管理。 通常存储在堆(heap)上。 malloc:未定义;
calloc数据:0;
calloc指针:空;
new 基本数据类型:未定义;
new 对象:默认构造函数。
- 动态变量:
- 通常,“动态变量”这个词并不是用来描述变量作用域或存储期的标准术语。然而,在编程中,动态内存分配通常指的是使用
malloc
、calloc
、realloc
(在C中)或new
(在C++中)等函数在堆(heap)上分配的内存。- 这种内存的生命周期不是由作用域或存储期决定的,而是由程序员的显式管理(如
free
或delete
)决定的,程序员需要负责分配和释放这些变量所占用的内存。- 需要注意的是,动态分配的内存如果未被正确释放,会导致内存泄漏(memory leak),这是一种常见的编程错误。因此,在使用动态内存分配时,必须格外小心以确保内存的正确管理。
🐋5.5 静态成员与静态函数
🧩题目
构造点,并统计已构造的点个数。
📇算法思路
使用静态成员 count 统计已有点个数。
⌨️算法代码
#include <iostream>
using namespace std;
class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {
count++; // 在构造函数中对count累加,所有对象共同维护同一个count
}
Point(Point& p) {
x = p.x; y = p.y; count++;
}
~Point() { count--; } // 每一个点消亡时,使用析构函数--,一般构造函数与析构函数执行的功能是相对的
int getX() { return x; }
int getY() { return y; }
static void showCount() { // 静态函数
cout << "Object count = " << count << endl;
}
private:
int x, y;
static int count; // 静态成员
};
int Point::count = 0;
int main()
{
Point::showCount();
Point a(4, 5);
cout << "Point A:" << a.getX() << "," << a.getY() << "\t";
a.showCount(); // 或 Point::showCount();
Point b(a);
cout << "Point B:" << b.getX() << "," << b.getY() << "\t";
b.showCount(); // 或 Point::showCount();
return 0;
}
📇执行结果
📇代码解释
在创建Point的对象时,静态成员 count 的数量自增,相应的,调用析构函数时 count 的数量自减。
值得注意的是,如果
showcount()
是静态函数,它可以直接通过类名调用,而不需要类的实例。在编译时,编译器会为静态函数分配存储区,并且该函数可以在没有创建类实例的情况下执行。相反,如果
showcount()
不是静态函数,那么它必须通过类的实例来调用。尝试在没有实例的情况下调用非静态函数(例如,当count = 0
时,如果没有任何Point
对象存在),编译器会报错,因为它违反了C++的调用规则。然而,一旦创建对象后调用了非静态函数
showcount()
,无论count
的值是多少(包括0),它都能够正确地显示count
的值。
📇相关知识
静态函数和非静态函数 静态函数 非静态函数 可见性和生命周期 只在定义它的文件内部可见,不能被其他文件调用,且在程序运行期间一直存在,不会被释放。 可以被当前文件以外的其他文件调用,具有全局可见性,且在被调用时动态地创建,在函数返回时被释放,生命周期相对较短。 内存分配 在程序开始时分配内存,直到程序结束时才释放内存,函数调用期间不再进行内存分配。 在每次被调用时都会从栈中分配内存,在调用结束后释放内存。 访问权限和对象状态 只能访问静态成员变量或其他静态函数,不能直接访问类的非静态成员变量和非静态成员函数,因为它们没有与特定对象相关联。 可以访问并修改对象的状态,包括非静态成员变量和非静态成员函数,具有访问特定对象的上下文。 调用方式 可以通过类名直接调用,无需创建类的实例。 必须通过类的实例来调用。 适用性 (1)限制函数可见性;
(2)节省内存空间。
(1)访问和修改对象状态的情况。
🐋5.6 友元函数与友元类
📇相关概念
友元函数(Friend Function)和友元类(Friend Class)是C++中提供的一种访问控制机制,允许特定的非成员函数或非同类的成员函数访问一个类的私有(private)和保护(protected)成员。
友元函数
友元函数是一个在类定义中被声明为
friend
的非成员函数。尽管它不是类的成员函数,但它被赋予了访问类私有和保护成员的权限。友元函数通常用于操作类的内部数据,而这些操作不适合作为类的成员函数来实现。#include <iostream> #include <cmath> using namespace std; class Point { public: Point(int x = 0, int y = 0):x(x), y(y) {}; int getX() { return x; } int getY() { return y; } friend float dist(Point& a, Point& b); // 友元函数 private: int x, y; }; float dist( Point& a, Point& b) { double x = a.x - b.x; double y = a.y - b.y; return static_cast<float>(sqrt(x * x + y * y)); } int main() { Point p1(1,1), p2(4,5); cout << "The distance is:"; cout << dist(p1, p2) << endl; return 0; }
友元类
友元类是指一个类被另一个类声明为
friend
。当一个类被声明为另一个类的友元时,它的所有成员函数都可以访问另一个类的私有和保护成员。这种关系不是相互的,除非双方都明确声明对方为友元。#include <iostream> #include <cmath> using namespace std; class A { friend class B; // 友元类 public: void display(){ cout << x << endl; } private: int x; }; class B { public: void set(int i); void display(); private: A a; }; void B::set(int i) { a.x = i; // 由于B是A的友元类,所以可以直接访问A的私有成员x } void B::display() { a.display(); // 调用A类的display方法 } int main() { B b; // 创建B类的实例 b.set(10); // 调用B类的set方法,设置A类实例a的私有成员x为10 b.display(); // 调用B类的display方法,这将间接调用A类的display方法显示x的值 return 0; }
注意:
- 友元函数和友元类都提供了对类私有成员的访问权限。
- 友元关系不是相互的,也不是继承的。如果一个类是另一个类的友元,这并不意味着反之亦然。
- 友元关系破坏了封装性,应谨慎使用。它破坏了数据隐藏的原则,可能会导致代码维护困难和错误的出现。通常,应优先考虑使用类的成员函数和公共接口来实现功能,而不是直接使用友元函数或友元类。
🐋5.7 共享数据的保护 / const关键字
📇相关概念
常数据成员
常数据成员是指在类中声明的常量成员变量。它们必须在类构造函数的初始化列表中进行初始化,并且一旦初始化后,它们的值就不能再被修改。
#include <iostream> using namespace std; class A { public: A(int i); void print(); private: const int a; static const int b; }; const int A::b = 10; // 常数据成员,必须初始化 A::A(int i) : a(i) {} void A::print() { cout << a << ":" << b << endl; } int main() { A a1(100), a2(0); a1.print(); a2.print(); return 0; }
常成员函数
常成员函数是指在函数声明后面添加了
const
关键字的成员函数。常成员函数不能修改它所属对象的任何数据成员(除了被声明为mutable
的数据成员)。这样的函数可以在常对象上被调用(普通对象也可以被调用)。#include <iostream> using namespace std; class R { public: R(int r1, int r2):r1(r1),r2(r2){} void print(); void print() const; private: int r1, r2; }; void R::print() { cout << r1 << ":" << r2 << endl; } void R::print() const { // 常函数,编译器会特别审查是否改变了函数的状态;普通对象也可以调用常函数。 cout << r1 << ":" << r2 << endl; } int main() { R a(5, 4); a.print(); const R b(20, 52); // 常对象 b.print(); return 0; }
常量表达式和constexpr
除了上面提到的常成员函数外,C++11还引入了
constexpr
成员函数,这些函数在编译时或运行时都能被计算,但它们在编译时必须产生常量表达式。然而,由于constexpr
函数的限制,它们通常只用于非常简单的计算和操作。constexpr int z = 5; // z是一个编译时常量
#include <iostream> class Point { public: constexpr Point(int x, int y) : x_(x), y_(y) {} constexpr int getX() const { return x_; } constexpr int getY() const { return y_; } constexpr int sum() const { return x_ + y_; } private: int x_, y_; }; int main() { constexpr Point p(3, 4); // 在编译时构造Point对象 constexpr int sum = p.sum(); // 在编译时计算sum std::cout << "Sum of coordinates: " << sum << std::endl; // 输出7 // 下面的代码在编译时会出错,因为p不是constexpr对象 // Point q(5, 6); // constexpr int sum2 = q.sum(); // 错误:q不是constexpr对象 return 0; }
常引用作形参
常引用作形参意味着在函数内部不能通过这个引用来修改所引用的对象。这保证了函数不会意外地修改传递给它的参数。
#include <iostream> #include <cmath> using namespace std; class Point { public: Point(int x = 0, int y = 0):x(x), y(y){} int getX() { return x; } int getY() { return y; } friend float dist(const Point& p1, const Point& p2); // 常引用,可以读取p1与p2,但是不能重新赋值 private: int x, y; }; float dist(const Point& p1, const Point& p2) { double x = p1.x - p2.x; double y = p1.y - p2.y; return static_cast<float>(sqrt(x * x + y * y)); } int main() { const Point myp1(1, 1), myp2(4, 5); cout << "The distance is:"; cout << dist(myp1, myp2) << endl; return 0; }
函数返回常引用
确保函数返回的对象不会被修改。这通常用于返回类内部的私有成员变量,同时保证这些变量不会被外部修改。
class MyClass { private: int value; public: const int& getValue() const { // 返回常引用 return value; } };
常指针
指向常量的指针:指针指向的内容不能被修改。
const int x = 10; // 常量x const int y = 20; // 常量y const int *p = &x; // 指向常量的指针p,指向x // 下面的赋值是合法的,因为改变的是指针p所指向的地址,而不是它所指向的内容 p = &y; // 现在p指向y // 下面的赋值是不合法的,因为p指向的内容是常量,不能被修改 // *p = 30; // 编译错误:表达式必须是可修改的左值
常量指针:指针本身的值(即它所存储的地址)不能被修改,但它指向的内容可以修改。
指向常量的常量指针:指针本身和它指向的内容都不能被修改。
int a = 10; int b = 20; // 常量指针,指向的地址不能被修改,但指向的内容可以修改 int *const q = &a; // q = &b; // 编译错误:q是一个常量指针,它的值不能被修改 *q = 30; // 合法:修改q所指向的内容 // 指向常量的常量指针,既不能修改指向的地址,也不能修改指向的内容 const int *const r = &a; // r = &b; // 编译错误:r是一个常量指针,它的值不能被修改 // *r = 40; // 编译错误:r指向的内容是常量,不能被修改
🐋5.10 多文件工程
🧩题目
构造点,并统计已构造的点个数,要求使用多文件完成。
📇算法思路
类的定义放入point 点中。
⌨️算法代码
Point.h
class Point {
public:
Point(int x = 0, int y = 0) :x(x), y(y) {}
Point(const Point& p);
int getX() { return x; }
int getY() { return y; }
static void showCount(); // 静态函数成员
private:
int x, y;
static int count; // 静态数据成员
};
main
#include "Point.h"
#include <iostream>
using namespace std;
int Point::count = 0;
Point::Point(const Point& p) :x(p.x), y(p.y) {
count++;
}
void Point::showCount() {
cout << "Object count = " << count << endl;
}
int main()
{
Point a(4, 5);
cout << "Point A:" << a.getX() << "," << a.getY();
Point::showCount();
Point b(a);
cout << "Point B:" << b.getX() << "," << b.getY();
Point::showCount();
return 0;
}
📇执行结果
📇相关概念
头文件#include "Point.h",用“”时编译器会优先搜寻在当前的源文件目录;
头文件#include <iostream>,用<>是编译器会搜索标准库的安装路径。
🔚结语
博文到此结束,写得模糊或者有误之处,期待小伙伴留言讨论与批评,督促博主优化内容{例如有错误、难理解、不简洁、缺功能}等,博主会顶锅前来修改~~😶🌫️😶🌫️
我是梅头脑,本片博文若有帮助,欢迎小伙伴动动可爱的小手默默给个赞支持一下,感谢点赞小伙伴对于博主的支持~~🌟🌟
同系列的博文:🌸数据结构_梅头脑_的博客-CSDN博客
同博主的博文:🌸随笔03 笔记整理-CSDN博客