💻 CSC3200 LEC1-2 C++入门
CSC3200 LEC 1-2 CPP 入门
一、怎么运行?
MacOS 自带 clang++,你可以在终端查询:
clang++ --version
没有就 brew install clang++ 或者 brew install g++
运行一个项目文件:hello.cpp
clang++ -std=c++17 -Wall -O2 #filename#.cpp -o #compilename#
clang++ #filename#.cpp -o #compilename# // 也可以这么编译,比较快,但重要代码不建议
./#compilename# // 运行
Win 可以用 Dev-C++,但如果要用 VScode 之类的内置终端运行,可能需要下载 MSYS2 / MinGW 之类的编译器,具体不太清楚
多项目运行
src/ math.h, math.cpp, main.cpp
clang++ -std=c++17 -Wall -O2 src/main.cpp src/math.cpp -Iinclude -o app
./app
二、数据类型、计算符与命名规则
数据类型
using namespace std;
int age = 20; // 整数
double pi = 3.14; // 浮点数
char grade = 'A'; // 字符
bool flag = true; // 布尔值
int constant; // 可以
const int constant; // 不可以,常值必须在声明时复制,因为不可更改
注意,C 中有另一种声明常值的语句:#define constant 42;
但是它只对单文件有效,不同文件可以有不同值,容易导致故障。
计算符
int m = double(4) % 5; // 4
double n = int(4.2) / 2; // 2.0
double p = 4 / 5; // 0.0
// 所以要获得小数的精确值至少在算式中要有一个“double/float"类
#include <iomanip>
cout << setprecision(3) << 3.141592 << "/n"; // 保留三位小数
自增:(同样适用于减法)
int a = 0;
int b = 0;
int x = a++; // 赋值然后自增:a=1, x=0
int y = ++b; // 自增然后赋值:b=1, y=1
布尔运算符
-1 && double(2.0) == bool(true) // 非 0 数皆为真,真且真为真
"0" || NULL != false // 非空字符(串)都为真,真且假为真
三元运算符:
cout << ((a % 2 == 0) ? "Even" : "Odd") << endl;
// ? 前的条件真就输出: 前一项,反之就输出冒号后一项
string 类型需要单独引用,否则有可能失败 (因为 <iostream> 间接调用了 <string>,有一些情况下有概率可以编译)
using namespace std;
#include <string>
string name = "csc3200"
引用空间(库):#include <…> 不能写分号
引用本地 .h 文件:#include "….h" 同上
.cpp 文件无法被调用
命名规则
- 只能用字母、数字、下划线,且不能以数字开头。
- 区分大小写:Age 和 age 是不同变量。
- 不能使用关键字(如 int、if、class 等)。所有 cpp 保留词都在
stdnamespace 中。 - 常见风格:camelCase(小驼峰),或 snake_case。
所有关键词全部存储在一个叫做 std 的 namespace 中,调用时在代码顶部声明:
using namespace std; //只引用一个:using std::string//
#include <string>
string name = "csc3200";
否则每次使用都要写:
std::string name = "csc3200";
三、Standard I/O
cout
- short for “character output”,代表标准输出流(standard output stream),通常就是屏幕
- 来自头文件 #include <iostream> 例:
#include <iostream>
using namespace std;
int main() {
cout << "Hello, world!"; // 向屏幕输出
return 0;
}
« (流输出运算符)
- stream insertion operator
- 作用是把右边的内容“插入”到左边的输出流中。
- 无论什么情况,
<<都不会自动在多个输出项中间自动加空格。 例1:
cout << "答案是: " << 42;
- string “答案是: " 被插入到 cout 里 → 显示在屏幕上。
- 然后 42 也被插入到同一个流中 → 紧接着显示。
- output:
答案是: 42
例2:
#include <string>
#include <iostream>
string s = "Hello";
s += " World"; // 拼接
cout << s << " 长度: " << s.length() << endl;
- output:
Hello World 长度: 11
endl
- short for “end line”
- 作用是换行(相当于 “\n” 转义字符),同时刷新输出缓冲区
- 来自头文件 #include <iostream> 例:
#include <iostream>
cout << "1" << endl << "2"
cout << "3\n" << "4";
- output:
1
2
3
4
区别:
\n只是换行转义符endl= 换行 + 强制刷新缓冲区(立即显示)- 当遇到
\n并且输出目标是 终端 时,通常能看到换行。但如果输出目标是 文件 或 管道,则 \n 不会触发刷新,可能要等缓冲区满才写入
» (流提取运算符)
一般是配合 cin(character input),从键盘读取信息:
#include <iostream>
#include <string>
using namespace std;
int main() {
int age;
string name;
cout << "请输入姓名和年龄: ";
cin >> name >> age; // 变量之间空格或换行皆可
cout << "结果: " << name << " " << age << endl;
return 0;
}
如果不想被空格隔开,直接读整行:
string name;
getline(cin, name); // 读到回车为止
但是 getline() 函数前如果紧跟着另一个 cin 或 getline,会导致回车被作为变量读进去:
cin >> age; // 输入 18↵
cin.ignore(numeric_limits<streamsize>::max(), '\n');
getline(cin, name); // 省略上一行的话,这里直接读到空行!
四、函数定义与调用
main()是入口方法,自动运行,不要去调用它,可以有参数,返回值一定是整数型的,只有返回 0 才是正常退出状态码(也可以不写)。- 函数可以有多个
return,但在单次运行时只能遇到一次return,return只能返回声明的返回值类型,所以即使有条件语句也不能返回多种数据类型。
例:
#include <iostream>
using namespace std;
// 计算差值
int diff(int a, int b) {
if (a > b) return a - b;
if (a <= b) return b - a;
}
int main() {
cout << diff(3, 5) << endl;
return 0; // cpp-11 以后的版本中可以省略
}
- 格式:
返回类型 函数名(参数列表) {
// 函数体
return 值;
}
五、条件结构
int x = 10;
if (x > 0) {
cout << "positive" << endl;
} else if (x == 0) {
cout << "zero" << endl;
} else {
cout << "negative" << endl;
}
switch:
switch (#变量或表达式#) {
case1:
#条件#;
break;
default:
#条件#;
break;
}
六、循环结构
for 循环
for (int i = 0; i < 5; i++) {
cout << i << " ";
}
输出:0 1 2 3 4
while 循环
int i = 0;
while (i < 5) {
cout << i << " ";
i++;
}
do-while 循环
int i = 0;
do {
cout << i << " ";
i++;
} while (i < 5);
(至少执行一次)
七、地址,指针,引用和传递
&a:取用变量 a 的地址
int& r = a:引用 a (给 a 另一个名字 r)
int* p = &a:声明 p 属于 a 的指针
*p:取用指针 p 对应变量的值
int a = 5;
int* p = &a;
int& r = a; // r 引用 a
cout << *p; // 输出 12 位 16 进制整数
*p = 20; //指针也可以直接修改对应变量的值
p = &r; //指针可以改变对象,但此时位置不变,因为 r 引用 a
cout << (&r == &a) << "\n"; // return 1 (C++ 里输出 true 会变成 1)
const int& r = a:常量引用 (不可以通过 r 修改 a 值) const ref.
int* const p = &a:指针常量 (可以修改 a 值,不能改指向别的变量) const pointer
const int* p = &a:常量指针 (不可以修改 a 值,可以改指向别的变量) pointer of const
const int* const p = &x:常量指针常量…… const pointer of const
- 指针传参:(修改但不复制 a, 传 a 的指针 p 给函数,可兼容 C)
void setZero(int* p) { *p = 0; }
setZero(&a); // a 变成 0
- 引用传参:(修改单不复制 a,传 a 的别名 b 给函数,不可兼容 C)
void SetZero(int& b) { b = 0; }
SetZero(a);
- 按值传参:(不修改 a 的值,只修改传给函数的复制体 b 的值)浅拷贝
void setZero(int b) { b = 0; }
setZero(a);
- 常量引用传参:
void SetZero(const int& b) { /* b = 0 在这里是无效的 */ }
SetZero(a); // 会报错
八、类与对象(实例)——OOP
- 如果 int 是类,42 就是一个实例
- 对象都是指某种类的实例
- 类中的函数——成员函数,就是“方法”
- 实例化就是赋予成员变量以值
- 类的成员变量一般是 private 的
- 在每个类中,constructor 可以有多个,destructor 只有一个
- 在一个完整的类结束后,最后一个大括号后要加分号
- rule of three
类的基础模型:
class 类 {
public:
返回类型 成员函数(函数参数) {
成员变量 = 函数参数;
}
private:
数据类型 成员变量;
}
对象 obj 要调用类 C 中的定义的成员函数 func(int, v):
C obj;
obj.func(3);
对象要调用类的成员变量 variab:
C obj;
obj.variab = 3;
对象的声明:
IntCell m1; // OK:默认构造
IntCell m2(12); // OK:有参构造
IntCell* m3; // OK:指针声明
Intcell m4(); // 这其实是函数声明,不是对象!
m3->function(); // 指针访问成员要用箭头
m1.function();
接口-实现 分离
IntCell.h:(头文件,包括类声明、构造方法)
- 头文件也可以用
\#pragma once防重包含。
#ifndef INTCELL_H // 防止重复包含
#define INTCELL_H
class IntCell {
public:
IntCell(int initialValue = 0); // 构造函数声明
int read() const; // 读取
void write(int x); // 写入
private:
int storedValue; // 私有成员
};
#endif
IntCell.cpp:
#include "IntCell.h"
IntCell::IntCell(int initialValue) // 类::成员函数(变量)
: storedValue(initialValue) { }
int IntCell::read() const {
return storedValue;
}
void IntCell::write(int x) {
storedValue = x;
}
this 指针
- 成员函数和普通裸函数的区别就是:它有一个隐式的 this 参数(指针)。
- 在类被实例化的过程中,每一个对象(实例)调用类的成员函数的时候,编译器会把对象的地址传进类中。this 是一个指针,指向当前调用这个函数的对象的位置。
this->value = v:把调用函数的对象里的 value 成员变量的值改成 v。- -> 是“通过指针访问成员”的运算符
this->value就等价于(*this).value。可以理解为 “this 指向的位置对应的对象的 value 变量的值”的意思- value 本来就是成员变量,你可以直接写 value = v。但如果函数参数和成员变量同名:
class Tick {
int value;
public:
void setValue(int value) {
this->value = value; // 左边是成员变量,右边是参数
}
};
Tick b1, b2; // 将 Tick 实例化,产生对象
b1.setValue = 10;
b2.setValue = 20;
cout << b1.value << " " << b2.value; // 输出 10 20
Rule of Three
Copy Constructor(拷贝构造函数)、Copy Assignment Operator(拷贝赋值运算符)、Destructor(析构函数)必须同时在一个自定义类里被定义。
Copy Constructor
- 用一个对象初始化另一个全新对象时调用的方法
class MyArray {
int* data;
int size;
public:
MyArray(int s) : size(s) {data = new int[size];}
// ✅ 拷贝构造函数
MyArray(const MyArray& other) {
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) data[i] = other.data[i];
}
};
MyArray a1(5);
MyArray a2 = a1; // 调用运行
上述 Copy Constructor 深拷贝了 MyArray 的一个实例 other,确保新数组的存储位置与 other 不同,防止 Destructor 在清理内存时全部删除,导致程序崩溃。
Copy Assignment Operator
- 对已存在的对象赋值时调用
class MyArray {
int* data;
int size;
public:
MyArray(int s) : size(s) { data = new int[size]; }
// ✅ 拷贝赋值运算符
MyArray& operator=(const MyArray& other) {
if (this != &other) { // 防止自赋值
delete[] data; // 先释放旧资源
size = other.size;
data = new int[size];
for (int i = 0; i < size; i++) data[i] = other.data[i];
}
return *this; // 允许链式赋值 a = b = c;
}
};
MyArray a1(5);
MyArray a2(10);
a2 = a1; // 调用运行
保证赋值时安全释放旧资源,再分配并复制新资源。
Destructor
- 需要释放不必要内存时使用
class MyArray {
int* data;
int size;
public:
MyArray(int s) : size(s) { data = new int[size]; }
// ✅ 析构函数
~MyArray() {
delete[] data; // 释放内存
}
};
// 对象遇到这一段自动销毁