553 C++程序设计 学习笔记
注意:本文的组织结构略有混乱
谨以此文纪念我逝去的那一个多月
数据类型和运算符 计算数组长度 int n = sizeof (arr) / sizeof (arr[0 ]);
枚举类型
运算符 *
运算符
上下文
含义
示例代码
数学运算
乘法
int result = a * b;
指针声明
定义指针
int* ptr = &x;
解引用
访问指针指向的值
int value = *ptr;
不能重载的运算符 成员访问运算符 .
成员指针访问运算符 .*
->*
域运算符 ::
长度运算符 sizeof
三元条件运算符 ?:
必须以成员函数形式重载的运算符 赋值运算符 =
下标运算符 []
函数调用运算符 ()
#include <iostream> using namespace std;class Calculator {private : int value; public : Calculator (int initialValue = 0 ) : value (initialValue) {} int operator () (int a, int b) { return value + a + b; } int operator () (int a, int b, int c) { return value + a + b + c; } }; int main () { Calculator calc (5 ) ; cout << "calc(10, 15) = " << calc (10 , 15 ) << endl; cout << "calc(10, 15, 20) = " << calc (10 , 15 , 20 ) << endl; return 0 ; }
成员选择运算符 ->
存储类说明符 存储类说明符(Storage Class Specifiers) 用于定义变量或函数的存储持续时间(storage duration)、作用域(scope)以及链接性(linkage)。它们提供了对变量和函数生命周期、可见性和内存管理方式的控制。
auto
含义 : auto
表示变量具有自动存储持续时间 。即变量在进入其作用域时被创建,在离开作用域时被销毁。
默认行为 : 在 C++11 之前,局部变量默认是 auto
类型(即使未显式声明)。从 C++11 开始,auto
被重新定义为类型推导关键字,用于自动推断变量的类型。
register
含义 : 建议编译器将变量存储在寄存器 中以提高访问速度。然而,现代编译器通常会忽略此建议,因为优化器已经足够智能。
限制 : register
变量不能取地址(即不能使用 &
运算符)。
注意 : 从 C++17 开始,register
关键字已被废弃 (deprecated),不再推荐使用。
static
对于局部变量 :static
使变量具有静态存储持续时间 ,即使它定义在函数内部。变量在程序启动时初始化(只初始化一次),并在程序结束时销毁 。
对于全局变量或函 数:static
限制其链接性为内部链接 (internal linkage),即只能在当前文件中访问 。
extern
含义 : extern
用于声明一个变量或函数是在其他文件中定义的,表示该变量或函数具有外部链接性 (external linkage)。
extern
不分配内存,仅声明变量或函数的存在。
存储类说明符
存储持续时间
作用域
链接性
备注
auto
自动
局部
无
C++11 后主要用于类型推导
register
自动
局部
无
已废弃,现代编译器忽略
static
静态
局部/全局
无/内部
局部变量只初始化一次
extern
静态
全局
外部
声明外部变量或函数
语句 控制结构 顺序、选择、循环
switch 原则上每个 case
语句末尾都要写 break
,若没写,则发生“贯穿”,执行所有后续语句(包括 default
,直到遇到 break
int main () { int x = 2 ; switch (x) { case 1 : cout << "1" << endl; case 2 : cout << "2" << endl; case -2 : cout << "-2" << endl; default : cout << "default" << endl; } return 0 ; } 输出: 2 -2 default
函数 函数模板 函数模板是编写通用函数 的蓝图,允许为不同类型生成逻辑相同的函数。通过模板参数(如 typename T
),可以在编译时自动推导或显式指定具体类型。
template <typename T>void swap (T &a, T &b) { T temp = a; a = b; b = temp; } int main () { int x = 1 , y = 2 ; swap (x, y); double a = 3.14 , b = 2.71 ; swap (a, b); }
函数重载 允许在同一作用域内定义多个同名函数 ,但要求它们的参数列表(参数类型、数量或顺序)不同。编译器根据调用时传入的实参类型选择最匹配的版本。
void print (int value) { std::cout << "Integer: " << value << std::endl; } void print (double value) { std::cout << "Double: " << value << std::endl; } void print (const char * str) { std::cout << "String: " << str << std::endl; } int main () { print (10 ); print (3.14 ); print ("Hello" ); }
重载模板函数 重载模板函数指在同一作用域内定义多个同名函数模板 或与非模板函数 共存,这些模板或函数具有不同的参数列表(如参数类型、数量或模板参数数量)。编译器根据调用时的实参类型和模板推导规则选择最匹配的版本。
编译器首选函数名和参数类型匹配的具体函数,再找模板
类和对象 浅拷贝 深拷贝
浅拷贝 :直接复制对象的成员变量(包括指针)。如果成员是指针,则两个对象共享同一块内存,可能导致双重释放等问题。
深拷贝 :为每个对象分配独立的内存空间,两个对象互不影响。
指针成员变量的处理
class MyClass { private : int * data; public : MyClass (int value) { data = new int (value); } MyClass (const MyClass& other) { data = new int (*other.data); } ~MyClass () { delete data; } void setValue (int value) { *data = value; } int getValue () const { return *data; } }; int main () { MyClass obj1 (42 ) ; MyClass obj2 = obj1; obj1.setValue (100 ); cout << "obj1.value: " << obj1.getValue () << endl; cout << "obj2.value: " << obj2.getValue () << endl; return 0 ; }
成员函数的实现 返回值 类名 :: 函数名( 参数列表 ){ 函数体 }
构造函数 无参构造函数的实现 类名 :: 类名(){ 函数体 }
带参构造函数 定义:
类名( int 参数名1 = 5, int 参数名2 = 10);
实现:
类名 :: 类名( int 参数名1, int 参数名2){
成员变量 = 参数名1;
成员变量 = 参数名2;
}
带参构造函数(初始化列表) 实现:
类名 :: 类名(int 参数名1, int 参数名2) : 成员变量(参数名1), 成员变量(参数名2){ 函数体 }
析构函数 定义
实现
复制构造函数 定义
实现
Box::Box (const Box& other){ len = other.len; }
何时调用
(新定义)用一个已存在的对象初始化一个新对象 :
MyClass obj1; MyClass obj2 = obj1;
(作为函数参数)将对象作为值传递给函数 :
void func (MyClass obj) { }MyClass obj1; func (obj1);
(作为函数返回值)从函数返回对象时 (某些情况下):
MyClass func () { MyClass obj; return obj; }
注意:对象之间的赋值 不 调用复制构造函数 ,而是调用赋值运算符
静态成员 所有该类的对象共享同一个静态成员
可以通过类名直接访问和修改 静态成员
静态数据成员 存储在全局数据区(静态存储区),而不是栈或堆中
必须在类外进行定义和初始化(除非是const
或constexpr
类型的整型/枚举类型)。
class MyClass { public : static int Var; }; int MyClass::Var = 10 ;
静态成员函数 静态成员函数不能访问非静态成员 (包括非静态变量和非静态函数),因为它们不依赖于具体的对象实例。
可以在类中定义。
友元 允许某些函数或类访问另一个类的私有(private
)和保护(protected
)成员
违背了面向对象编程的封装原则
友元函数 友元函数是一个普通的全局函数 或另一个类的成员函数 ,但它被授予了访问某个类的私有和保护成员的权限。
不能通过类的对象调用
class MyClass { private : int privateData; public : MyClass (int data) : priavteData (data){} friend void func (MyClass &obj) ; }; void func (MyClass &obj) { cout << obj.privateData; }
友元类 一个类被授予了访问另一个类的私有和保护成员的权限
友元类的所有成员函数都可以访问被授予友元权限的类的私有和保护成员
class MyClass { private : int privateData; public : MyClass (int data) : priavteData (data){} friend class FirendClass ; }; class FriendClass { public : void func (MyClass &obj) { cout<<obj.privateData; } }
程序结构 标识符的作用域
作用域类型
描述
示例
函数原型作用域
仅在函数声明的参数列表内有效,参数名称只在该声明中起作用,不会影响其他地方。
double getArea(double radius); // "radius" 仅在括号内有效
块作用域
在一对大括号 { } 内声明的标识符,其有效范围限定于该大括号内;局部变量即属于此范畴。
{ int a = 10; // a 的作用域仅在此块内}
函数作用域
用于表示函数内部的标签(例如 goto 标签)的作用范围,这些标签在整个函数内都可见。
void func() { label: /*代码*/ goto label; // label 在整个函数内可用}
类作用域
类中声明的成员变量与成员函数的名称只在类内部有效,可通过作用域运算符访问或在类内直接引用。
class MyClass {public: int member;};MyClass obj;obj.member = 5;
命名空间/全局/文件作用域
在命名空间中(包括全局命名空间)声明的标识符,在整个命名空间或程序中均有效;对于使用 static 关键字声明的标识符,其作用域限于当前文件。
namespace NS { int n;}NS::n = 42; // n 在 NS 命名空间内有效
生命周期 #include <iostream> using namespace std;class A {private : int xx; public : A (int x) : xx (x) { cout << "A() " << xx << endl; } ~A () { cout << "~A() " << xx << endl; } }; A a (1 ) ; int main () { A b (2 ) ; static A c (3 ) ; }
构造函数调用顺序
全局对象 a(1)
在 main()
函数执行前初始化
局部对象 b(2)
在进入 main()
后按声明顺序初始化
静态局部对象 c(3)
在首次执行到声明位置时初始化
析构函数调用顺序
局部对象 b(2)
在 main()
函数结束(作用域退出)时销毁
静态局部对象 c(3)
在程序结束时销毁
全局对象 a(1)
最后销毁
控制台输出结果
text A() 1 ← 全局对象构造 A() 2 ← 局部对象构造 A() 3 ← 静态对象构造 ~A() 2 ← 局部对象析构(main结束) ~A() 3 ← 静态对象析构(程序结束阶段) ~A() 1 ← 全局对象析构(最后执行)
关键机制
存储类别
全局对象:静态存储期
static
局部对象:静态存储期
普通局部对象:自动存储期
生命周期规则 静态存储期对象按构造的逆序 析构,自动存储期对象在作用域结束时立即析构。
初始化时机 静态局部对象在首次执行到声明位置时初始化(本例中在 main()
首次执行时初始化)。
指针 动态内存分配 分配单个对象
int *p = new int ; int *q = new int (2 );
分配数组
释放内存
delete 指针名; delete [] 指针名;
继承和派生 概念
继承是指一个类(称为派生类 或子类)从另一个类(称为基类 或父类)中获取成员变量和成员函数的能力。
派生是指创建一个新的类(派生类),并基于已有的类(基类)进行扩展或修改的过程。
语法 定义
class 派生类名 : 访问控制 基类名 { };
public
:公有继承,基类的公有和保护 成员在派生类中保持 原有的访问权限。
protected
:保护继承,基类的公有和保护成员在派生类中变为保护 成员。
private
:私有继承,基类的公有和保护成员在派生类中变为私有 成员。
基类的私有成员不能被派生类直接访问
多重继承 class A {};class B {};class C : public A, public B {};
菱形继承
数据冗余 :D
中有两个独立的 A
实例。
二义性 :如果访问 A
的成员,编译器无法确定应该使用哪个副本。
class A {};class B : public A {};class C : public A {};class D : public B, public C {};
虚继承 “虚基类”是“虚继承”的结果,而“虚继承”是实现“虚基类”的手段
class A {};class B : virtual public A {};class C : virtual public A {};class D : public B, public C {};
多态 c++实现多态的方式:
编译时多态:函数重载、运算符重载
运行时多态:虚函数(以及函数覆写)
运算符重载 友元 需要对称性操作时,用友元
class Vector2D { private : double x, y; public : Vector2D (double x_v = 0 , double y_v = 0 ) : x (x_v), y (y_v){} friend Vector2D operator +(const Vector2D& lhs, const Vector2d& rhs); friend Vector2D operator +(double scalar, const Vector2d& vec); } Vector2D operator +(const Vector2D& lhs, const Vector2D& rhs) { return Vector2D (lhs.x + rhs.x, lhs.y + rhs.y); } Vector2D operator +(double scalar, const Vector2D& vec) { return Vector2D (scalar + vec.x, scalar + vec.y); }
需要访问类的私有或保护成员时,用友元
重载流插入运算符 <<
时,由于其第一个参数 std::ostream
不属于类的一部分,因此无法通过成员函数实现
class Vector2D {private : double x, y; public : Vector2D (double x_val = 0 , double y_val = 0 ) : x (x_val), y (y_val) {} friend ostream& operator <<(ostream& os, const Vector2D& vec); }; ostream& operator <<(ostream& os, const Vector2D& vec) { os << "(" << vec.x << ", " << vec.y << ")" ; return os; }
引用 返回当前对象本身
赋值运算符 =
或复合赋值运算符 +=
MyClass& operator =(const MyClass& other) { if (this != &other) { value = other.value; } return *this ; }
返回流对象
流插入运算符 <<
和流提取运算符 >>
class MyClass {private : int value; public : MyClass (int v = 0 ) : value (v) {} friend ostream& operator <<(ostream& os, const MyClass& obj); }; ostream& operator <<(ostream& os, const MyClass& obj) { os << obj.value; return os; }
返回可修改的对象
重载下标运算符 []
class IntArray {private : int data[10 ]; public : int & operator [](int index) { return data[index]; } const int & operator [](int index) const { return data[index]; } };
++运算符
后置++的int
参数是一个哑参数,只用于区分前置和后置版本
class Counter {private : int value; public : Counter (int val = 0 ) : value (val) {} int getValue () const { return value; } friend Counter& operator ++(Counter& c); friend Counter operator ++(Counter& c, int ); }; Counter& operator ++(Counter& c) { ++c.value; return c; } Counter operator ++(Counter& c, int ) { Counter temp = c; ++c.value; return temp; }
虚函数
当基类指针绑定派生类对象时,若基类和派生类都定义了一个普通的同名函数(非虚函数),会调用基类 的函数。
最佳实践:如果一个基类含有虚函数,则其析构函数应声明为虚函数
若析构函数不是虚函数,则只会调用基类的虚构函数。但构造函数都会调用到。
warning: delete called on non-final ‘A’ that has virtual functions but non-virtual destructor
虚函数 (virtual function
)是一种特殊的成员函数,它允许在派生类中重新定义基类的函数,并通过基类指针 或引用调用时实现动态绑定(也称为运行时多态)。
允许基类和派生类共享相同的接口,但具体的行为由派生类决定
虚函数的动态绑定需要维护虚函数表、虚表指针
虚函数不能是静态成员函数
虚函数不能是构造函数
#include <iostream> using namespace std;class Animal {public : virtual void speak () { cout << "Animal speaks" << endl; } virtual ~Animal () {} }; class Dog : public Animal {public : void speak () override { cout << "Dog barks" << endl; } }; class Cat : public Animal {public : void speak () override { cout << "Cat meows" << endl; } }; int main () { Animal* animal1 = new Dog (); Animal* animal2 = new Cat (); animal1->speak (); animal2->speak (); delete animal1; delete animal2; return 0 ; }
空基类指针 根据条件分配对象
class Base {public : virtual void show () { cout << "Base class show()" << endl; } virtual ~Base () {} }; class Derived1 : public Base {public : void show () override { cout << "Derived1 class show()" << endl; } }; class Derived2 : public Base {public : void show () override { cout << "Derived2 class show()" << endl; } }; int main () { Base* ptr = nullptr ; int choice; cout << "Enter choice (1 or 2): " ; cin >> choice; if (choice == 1 ) { ptr = new Derived1 (); } else if (choice == 2 ) { ptr = new Derived2 (); } if (ptr) { ptr->show (); delete ptr; } else { cout << "Invalid choice!" << endl; } return 0 ; }
纯虚函数 抽象类 拥有纯虚函数的类是抽象类
纯虚函数 强制 派生类实现它
抽象类不能被实例化
#include <iostream> using namespace std;class Shape {public : virtual void draw () = 0 ; virtual ~Shape () {} }; class Circle : public Shape {public : void draw () override { cout << "Drawing a circle" << endl; } }; class Rectangle : public Shape {public : void draw () override { cout << "Drawing a rectangle" << endl; } }; int main () { Shape* shape1 = new Circle (); Shape* shape2 = new Rectangle (); shape1->draw (); shape2->draw (); delete shape1; delete shape2; return 0 ; }
文件、流、字符串 标准输入输出流
std::cin
:标准输入流,通常与键盘关联。
std::cout
:标准输出流,通常与屏幕关联。
std::cerr
:标准错误流,用于输出错误信息,不带缓冲。
std::clog
:标准日志流,用于输出日志信息,带缓冲。
文件操作 C++提供了以下三种主要的文件流类,定义在头文件 <fstream>
中:
std::ifstream
:用于从文件中读取数据(输入文件流)。
std::ofstream
:用于向文件中写入数据(输出文件流)。
std::fstream
:既可以读取也可以写入文件(双向文件流)。
写入文件 #include <iostream> #include <fstream> using namespace std;int main () { ofstream outFile ("file.out" ) if (!outFile) { cerr << "Error opening file!" << endl; return 1 ; } outFile << "Hello, File!" << endl; outFile.close (); cout << "Data written to file." << endl; return 0 ; }
读取文件 #include <iostream> #include <fstream> #include <string> using namespace std;int main () { ifstream inFile ("example.txt" ) ; if (!inFile) { cerr << "Error opening file!" << endl; return 1 ; } string line; while (getline (inFile, line)) { cout << line << endl; } inFile.close (); return 0 ; }
输入字符串流 std::istringstream
的核心功能是将字符串视为输入流,并允许使用流操作符(如 >>
)从中提取数据
std::cin
:从标准输入(如键盘)读取数据。
std::istringstream
:从字符串中读取数据。
构造函数 std::string data = "123 456" ; std::istringstream iss (data) ;
提取数据
例题 若文件file.txt中有以下数据: C:123,5,40;T:1,2,32,50,60,3;R:6,8,8,100 其中,C的数据表示圆的圆心坐标和半径,T的数据表示三角形的三个坐标,R的数据表示矩形的两个对角的坐标。C++如何将这些数据读取出来,放在适当的变量中。
#include <iostream> #include <fstream> #include <sstream> #include <vector> #include <string> struct Circle { int x, y; int radius; }; struct Triangle { std::vector<int > points; }; struct Rectangle { int x1, y1, x2, y2; }; void parseData (const std::string& filename) { std::ifstream file (filename) ; if (!file.is_open ()) { std::cerr << "Error: Could not open file!" << std::endl; return ; } std::string line; Circle circle; Triangle triangle; Rectangle rectangle; while (std::getline (file, line)) { std::istringstream iss (line) ; char type; char colon; iss >> type >> colon; if (type == 'C' && colon == ':' ) { char comma; iss >> circle.x >> comma >> circle.y >> comma >> circle.radius; std::cout << "Circle: Center(" << circle.x << ", " << circle.y << "), Radius = " << circle.radius << std::endl; } else if (type == 'T' && colon == ':' ) { int value; char comma; triangle.points.clear (); while (iss >> value) { triangle.points.push_back (value); iss >> comma; } std::cout << "Triangle Points: " ; for (int point : triangle.points) { std::cout << point << " " ; } std::cout << std::endl; } else if (type == 'R' && colon == ':' ) { char comma; iss >> rectangle.x1 >> comma >> rectangle.y1 >> comma >> rectangle.x2 >> comma >> rectangle.y2; std::cout << "Rectangle: Diagonal((" << rectangle.x1 << ", " << rectangle.y1 << "), (" << rectangle.x2 << ", " << rectangle.y2 << "))" << std::endl; } else { std::cerr << "Error: Invalid format in line: " << line << std::endl; } } file.close (); } int main () { std::string filename = "file.txt" ; parseData (filename); return 0 ; }
与字符串有关的头文件
语言
头文件
用途说明
C
<string.h>
提供 C 风格字符串操作函数,如 strcpy()
、strcat()
、strlen()
、strcmp()
等,用于处理以 null 结尾的字符数组
C++
<string>
定义 C++ 标准库中的字符串类,如 std::string
和 std::wstring
,提供面向对象的字符串操作以及丰富的成员函数
C++
<cstring>
C++ 对应的 C 头文件版本,将 <string.h>
中的函数放入 std
命名空间中使用
cctype <cctype>
是 C++ 标准库中的一个头文件,它提供了许多用于字符处理的函数。这些函数主要用于检查或转换字符的属性,例如判断字符是否为字母、数字、空格等,或者将字符转换为大写或小写。
以下是 <cctype>
中一些常用函数的详细解释和示例:
常用函数列表 字符分类函数
这些函数用于检查字符的类型,返回值为布尔值(true
或 false
)。
函数名
功能描述
示例输入与输出
isalpha(c)
检查字符是否是字母(a-z 或 A-Z)。
isalpha('A') -> true
isdigit(c)
检查字符是否是数字(0-9)。
isdigit('5') -> true
isalnum(c)
检查字符是否是字母或数字。
isalnum('a') -> true
isspace(c)
检查字符是否是空白字符(空格、制表符、换行符等)。
isspace(' ') -> true
islower(c)
检查字符是否是小写字母(a-z)。
islower('b') -> true
isupper(c)
检查字符是否是大写字母(A-Z)。
isupper('B') -> true
ispunct(c)
检查字符是否是标点符号(非字母、数字、空白字符)。
ispunct('.') -> true
字符转换函数
这些函数用于对字符进行大小写转换。
函数名
功能描述
示例输入与输出
tolower(c)
将字符转换为小写(如果可能)。
tolower('A') -> 'a'
toupper(c)
将字符转换为大写(如果可能)。
toupper('b') -> 'B'
使用示例 以下是一个完整的代码示例,展示如何使用 <cctype>
中的函数:
#include <iostream> #include <cctype> using namespace std;int main () { char ch = 'A' ; cout << "isalpha('A'): " << isalpha (ch) << endl; cout << "isdigit('A'): " << isdigit (ch) << endl; cout << "isupper('A'): " << isupper (ch) << endl; cout << "islower('A'): " << islower (ch) << endl; cout << "tolower('A'): " << (char )tolower (ch) << endl; cout << "toupper('a'): " << (char )toupper ('a' ) << endl; cout << "ispunct('.'): " << ispunct ('.' ) << endl; cout << "ispunct('A'): " << ispunct ('A' ) << endl; return 0 ; }
运行结果:isalpha('A'): 1 isdigit('A'): 0 isupper('A'): 1 islower('A'): 0 tolower('A'): a toupper('a'): A ispunct('.'): 1 ispunct('A'): 0
substr()
的基本语法std::string substr(size_t pos = 0, size_t len = npos) const;
pos
: 子字符串的起始位置(索引从 0 开始)。
len
: 要提取的字符数。
返回值 : 返回一个新的 std::string
对象,表示从原字符串中提取的子字符串。
注意:
如果 pos
超出了字符串的长度,则会抛出 std::out_of_range
异常。
如果 len
超出了字符串剩余部分的长度,则会提取到字符串末尾。
find()
的基本语法size_t find (const string& str, size_t pos = 0 ) const ;size_t find (const char * s, size_t pos = 0 ) const ;size_t find (char ch, size_t pos = 0 ) const ;#include <iostream> #include <string> using namespace std;int main () { string str = "Hello, World!" ; size_t pos = str.find ("World" ); if (pos != string::npos) { cout << "Substring 'World' found at position: " << pos << endl; } else { cout << "Substring not found." << endl; } return 0 ; }
参数说明 :
str
或 s
: 要查找的子字符串(可以是 std::string
或 C 风格字符串)。
ch
: 要查找的单个字符。
pos
: 查找的起始位置,默认为 0
(从字符串开头开始查找)。
返回值 :
如果找到目标子字符串或字符,返回其首次出现的位置索引 (从 0 开始)。
如果未找到,返回 std::string::npos
(一个特殊常量,表示“未找到”)。
其他 随机数 #include <iostream> #include <cstdlib> #include <ctime> int main () { srand ((unsigned )time (0 )); int min = 10 , max = 20 ; int random_number = min + rand () % (max - min + 1 ); std::cout << "Random Number in range [" << min << ", " << max << "]: " << random_number << std::endl; return 0 ; }
接口和实现方法相分离的优点 模块换、低耦合:每个模块只依赖于定义良好的接口而不需要关心具体实现细节,从而降低了模块间的耦合度
易维护、高拓展:开发者可以在不改变接口的前提下修改或替换实现部分。
提高代码可重用性、可测试性