C/C++ 基础复习及补充
本文章仅为个人学习,如有错误之处请指正。
C标准库-注释-条件编译
C标准库在C++中可用,由x.h --> cx 如:string.h --> cstring stdio.h --> cstdio math.h --> cmath,有部分不变,如malloc.h
/*……*/
快注释 //
行注释
条件编译,如
#if 1
……
#endif
#if 0 …… #else …… #endif
#if 1 …… #elif …… #elif ……#endif
#ifdef XXX …… #endif
#ifndef XXX …… #else …… #endif
C++标准输出输入流-名字空间
iostream库 i in o out
cout是标准名字空间std的一个名字,使用必须加上名字空间限定std::
名字空间避免引用多个库中方法(函数)重名的影响
也可以用:using std::cout 或 引入整个名字空间:using namespace std
<< 输出流运算符 a << x 将数据x输出到a
std::endl 相当于 \n
cin 标准输入流对象 输入运算符>>
cin >> a >> b >> c;
cout << a << b << c;
对文件同理,如
#include<iostream>
#include<fstream>
#include<string>
using namespace std;
int main(){
ofstream oF("test.txt");
oF << 22 << " " << "Are you ok ?\n";
oF.close();
ifstream oF("test.txt");
double a;
string str;
iF >> a >> str;
cout << a << " " << str << endl;
return 0;
}
引用变量、引用形参
引用变量,相当于给一个变量去了一个别名
且引用变量“从一而终”,只能引用唯一已经定义的 其改变会改变所引用的变量
对引用变量的操作就是对其所引用的变量的操作
#include<iostream>
int main(){
int a = 1;
int r = &a; //r与a是同一块地址的名称
return 0;
}
引用变量和被引用的变量的变量类型必须匹配
引用形参,可以在函数中实现之间改变参数的值
C中有堆栈,存储局部数据
C中函数的形参是值参数,形参为函数的局部变量,有独立的内存块,调用时是将实参的值copy到(赋值)形参中,对此形参的改变不会影响实参 若要实现改变需要将形参设为指针变量(函数值的值形参:传递指针),传递(获取)实参的地址( &
获得实参地址),通过 *
获取地址的值对其修改
void fun_p(int *a,int *b){ //指针
*a--;
*b++;
} //使用 fun_p(&m,&n);
void fun_g(int &x,int &y){ //函数引用形参 引用实参 对实参进行修改
x--;
y++;
}
函数的默认形参、函数重载
默认形参 (可选参数) 形参可以有默认值 默认形参必须在非默认形参右边
int fun_1(int n,int m = 1){
;;
}
double fun_2(double n,double m = 1.1){
;;
}
函数重载 C++允许作用域内有同名函数,但形参应不同
函数的签名 = 函数名 + 形参列表
使用时寻找对应实参类型的函数
注:函数重载无法根据返回值的类型区分函数
函数模板
通用算法(泛型算法):函数模板 可以通过更改typename实例化对应不同数据类型(如:string int double float …… )的函数
用template关键字增加一个模板头,将数据类型变成类型模板参数
#if 1
#include<iostream>
template <typename T>
T fun_m(T n,T m){
return n + m;
}
int main(){
std::cout << fun_m<int>(1,2) <<std::endl; //int 为给模板传递需实例化的参数
std::cout << fun_m(1.1,2.2) <<std::endl; //编译器也会自动识别数据类型生成对应函数
return 0;
}
#endif
用户自定义类型string和vector
string 用户自定义类型 表示符串
可以用成员访问运算符 .
访问string类的成员 或用运算符对string对象进行运算,如 +、[]下标运算符
#include<iostream>
using namespace std;
int main(){
string sr_1 = "hello",sr_2("world");
s.size(); //返回字符串长度
string sr_3 s.substr(1,3); //相当于切片
string sr_4 = sr_1 + " " + sr_2;
sr_3[0] = 'A';
int pos = sr_4.sr_1.find("llo"); //find函数 返回llo字符串所在的位置 下标
sr_1.insert(1,"OK"); //在下标为1的位置插入
return 0;
}
vector
C/C++静态数组 内在数组 存储在程序内的堆栈中 定义后,大小固定无法
vector 向量(动态数组) 类似以数组 可动态增长 需使用 vector 库
是一个类模版 通过实例化产生一个类 如:vector<dounble>
产生一个数据元素为 double 的 vector<int>
类(向量)
可通过 vector<int>
类对象去访问其成员,如成员函数 也可用运算符进行一些运算
#include<iostream>
#include<vector>
using namespace std;
int main(){
int arr[] = {22,43,66}; //静态数组一旦定义后无法改变
std::vector<int> v_1 = {23,56,76}; //初始化
vector<int> v_2; //空的
v_1.push_back(88); //push_back(),最后添加元素
V_1.pop_back(); //与push_back(),删除最后一个元素
//在前面或中间插入数据 任意处插入删除 ?
//.size() 获取符串长度 []下标索引
v_1.resize(); //重新定义大小
}
指针和动态内存分配
指针 指针即地址 通过 &(取地址运算符) 获得变量的地址 &a
指针变量 存储指针的变量(只能存储对应类型的变量) 通过 *(取内容运算符) 可以得到一个指针变量所指向的变量
T *p; //p是存储"T类型变量的地址"的变量
*p: //获取p所指向的内容
int *p = &a;
int *q = p;
{ //指针访问数组元素
int arr[] = {12,34,45}; //局部变量 数据存放在堆栈区 程序内的
int *p = arr; //数组名就是数组的第一个元素的地址即 &(arr[0]) *p[1]即*(p+i)
cout << *(p+1) << " " << p[1] << arr[1] << endl;
for(int *q = p + 3;p < q;p++) cout << *P <<'\t';
} //访问必须在已经定义的元素
动态内存分配
堆存储区 所有程序共享的内存
new 用来申请内存块 delete 用于释放内存
C中 malloc free realloc
{
int *p = new int; //分配int大小的内存 指针存储在int类型指针变量p中
//C中使用 malloc 库实现动态内存分配
//C++此处使用 new 不仅能(申请)分配内存,还能给对象进行一定的初始化
*p = 3; //通过指针变量访问 new 申请的内存
delete p; //释放内存 如果不处理 会内存泄漏
p = new int;
delete p;
}
{
int n = 3;
int *p = new int[n]; //指针指向多个int型内存 p指向第一个 int[0]
//通过 p[i] *(p+i) 访问
for(int i = 0;i < n;i++) p[i] = i;
for(int *q = p + n;p < q;p++) cout << *P <<'\t';
char *s = (char *)p; //强制类型转化 int型 4字节 char型一个字节
//对与指针变量s认为指向的是char
int char_n = n * sizeof(int) / sizeof(char); //sizeof 返回字节长度
for(int i = 0;i < char_n;i++) s[i] = 'S' + i;
for(char *r = s + char_n;s < r;s++) cout << *s;
//delete p; 仅能释放第一个
delete[] p; //全部释放
}
{
T *p = new T;
delete p;
T *p = new T[n]; //组 n具体值 可以提前定义 如:int n = 3;
delete[] p;
}
类和对象
变量 对象 存储数据的内存块
过程 函数 对数据进行处理
面向对象编程
过程式编程:问题分为子问题,编写对应函数 C中一个程序由函数构成,函数由一系列指令语句构成(程序块) 数据和对数据处理的函数 分离
数据可被任何 函数访问 数据不安全 维护麻烦
面向对象编程:数据是隐藏的 程序由不同种类的对象相互协作完成 对象间通过发送/接受消息来协作完成各种任务 由面对对象编写的程序成为"对象式系统"
对象 由自己的属性和对应的(行为)功能 messages 识别有什么对象
- 对象设计
- 从具有共同特征的许多对象抽象出(类)某种概念 从此抽象实例化为具体对象
- 某些(类)概念之间可能存在某种关系关系 概念之间具有组合(包含)关系
- 具有派生关系、继承 – (类)概念之间具有继承(派生)关系
- 从具有共同特征的许多对象抽象出(类)某种概念 从此抽象实例化为具体对象
- 面向对象编程与过程式编程
- 过程式编程 用内在类型(概念)如:int float,用机器类型的概念去解决复杂问题,不易于思考问题
- 面向对象编程 使用符合人们思维的概念来思考问题,更方便理解、查错、组装(更进一步抽象)
- 用户自定义类型
- 自己定义自己"用户自定义类型",如类 类型,来表示各种应用问题的各种概念
- C++标准库已经提供了很多实用的"用户自定义类型"
- cout 是 ostream 类的对象(变量) cin 是istream 类的对象(变量) 可向其发送消息
- string是一个表示字符串的类,通过
.
访问 str.size() 茶寻该对象包含的字符数目
- 一个用户定义类型包括
- 属性
- 操作 运算
- 访问权限 私有 共有
- 靠虑多个用户定义类型的关系
- 具体对象如何交互协作 发送消息 使用成员函数
C++中定义类
用 class struct 关键字 定义的一个类就是一个数据类型
-
包含关系 从一个类中实例化的一个对象,包含不同类型的成员
-
类类型的变量通常称为对象
- 通过
.
访问对象成员
- 通过
-
与内在类型一样,可定义类类型的数组 存储一组类类型变量
-
可以定义类类型 指针类型变量 如:
T *
就是T指针类型T *p
p 为T指针类型变量- 指针变量可以指向一个类对象
- 间接访问运算符 -> 取内容运算符 *
- 如:(*p).xxxx p -> xxxx
- 指向动态分配的指针变量
- 使用 new 与 delete 关键字
-
类成员函数
-
类内定义函数
-
struct So_M{ string name; void fun_prt(){cout << name << endl;}; };
-
-
类外定义函数
-
struct So_M{ string name; void fun_prt(); //函数声明 }; void So_M::fun_prt(){ //建立So_M内的函数 So_M名字空间内的函数 //名字空间So_M 类作用域 如果没有则 为普通函数 cout << name << endl; };
-
-
实例:
struct Student{
string name;
int age;
};
//类类型的变量通常称为对象
Student stu_1; //实例化Student类 stu_1是Student类的一个实例
//通过 . 访问对象成员
stu_1.name = "jj";
stu_1.age = 20;
//与内在类型一样,可定义类类型的数组 存储一组类类型变量
Student stu_s[3];
//类类型指针类型变量
Student *p; //定义Student指针类型变量
p = stu_s + 2; //指向第三个stu_s 将其地址放到 p 指针变量中
(*p).name = "OK";
p -> age = 18 ;
//指向动态分配的对象
Student *q = new student; //存放在堆存储区 所有程序都可以访问
q -> age = 22;
delete q; //不用时释放内存防止 内存泄漏
q = new student[3]; //指向3个student类型的内存空间的起始地址 student[0] 的地址
p[1].age = 15;
*(q+2).age = 16; //可以通过 (*(q+1)) 以保证顺利访问
q -> age = 78;
delete[] q;
成员访问运算符 .
间接访问运算符 ->
取地址运算符 &
取内容运算符 *
学生成绩分析案例
MY_DO : 使用静态组 堆栈区
#if 1
#include<iostream>
#include<string>
using namespace std;
struct student{
string name;
int scroe;
void print();
};
void student::print(){
cout << name << " " << scroe <<endl;
}
int main(){
int i,j = 0;
int m,n = 5;
cout << "Inter number of students" << endl;
student stu_s[n];
for(i = 0;i < n;i++) stu_s[i].name = 'A' + i;
for(i = 0;i < n;i++) stu_s[i].scroe = 111 + i;
for(i = 0;i < n;i++) j += stu_s[i].scroe;
cout << "Average scroe is " << j/n << endl;
j = stu_s[0].scroe;
for(i = 1;i < n;i++)
if(j < stu_s[i].scroe){
j = stu_s[i].scroe;
m = i;
}
cout << "Max is ";
stu_s[m].print();
for(i = 1;i < n;i++)
if(j > stu_s[i].scroe){
j = stu_s[i].scroe;
m = i;
}
cout << "Min is ";
stu_s[m].print();
for(i = 0;i < n;i++) stu_s[i].print();
return 0;
}
#endif
使用动态组 vector自定义类型
#include<iostream>
#include<string>
#include<vector>
struct student{
string name;
int scroe;
void print(){
cout << name << " " << scroe <<endl;
}
};
int main(){
vector<student> students;
}
this 指针、访问控制、构造函数
this 指针:成员函数实际上隐含的一个this指针 这玩意相当于 python 中的 self 指向储存对象的地址 不过定义函数时不用传参
通过对象调用成员函数 C++中类的内部函数调用时编译器会将其转换为普通的外部函数
struct student{
string name;
int scroe;
void print(){ //此函数会被编译器转化为 参数为指针的外部函数
cout << this -> name << " " << this -> scroe <<endl;
} //故可以在成员函数中加上 this 关键字
};
//假定的转化
void print(student *this){ //转换后的函数 this 指针变量 指向调用此函数的对象
//this 存放 调用此函数的对象 的地址
cout << this -> name << " " << this -> scroe <<endl;
//通过this指针访问 对象的成员变量
}
student stu_1;
stu_1.print(); //相当于 print(&stu_1) &stu 指针
访问控制 使用 public private 关键字
- class 内的成员默认是 private(私有) 外部函数无法直接访问对象的私有成员
- public 任何函数都可以修改对象成员的数据
- private
- struct 内的成员默认是 public(共有、公开)
class student{
private: //设置 private成员 声明私有 有保护作用
string name;
int scroe;
public: //设置 public成员 声明共有 对外的接口
string name;
int scroe;
void print(){
cout << name << " " << scroe <<endl;
}
//同设置成员函数 访问修改私有成员变量 保证自己修改自己数据
string get name() { return name; }
string get age() { return age; }
void set_name(string n) { name = n; }
void set_age(double g) { age = g; }
};
构造函数
函数名与类名相同且无返回值类型的成员函数
- 如果没有在类中创建构造函数 C++默认生成
student(){}
的构造函数 - 定义了构造函数C++则不会生成默认的构造函数
class student{
private:
"string name;
"int scroe;
public:
string name;
int scroe;
student(){} //默认构造函数
student(string n,int g){ //不是默认构造函数
name = n; age = s;
}
void print(){
cout << name << " " << scroe <<endl;
}
};
int main(){
student stu_1; //在创建一个类对象时会自动调用称为"构造函数"的成员函数
//构造函数完成对对象的创建工作 如果未在类中定义构造函数C++会生成默认的构造函数
student stu_1("G",13);
student stu_s[3]; //创建定义类类型的数组 类必须有默认构造函数 否则无法成功 数组有多个对象 没法传参
return 0;
}
运算符重载
运算符重载:针对用户自定义类型(类定义)重新定义运算符函数
//定义输入输出流运算符 只能作为外部函数
class student{
private:
string name;
int scroe;
public:
student(string n,int g){ //不是默认构造函数
name = n; age = s;
}
//将外部函数声明为友元函数 使得其可以访问中的私有属性
friend ostream& operator<<(ostream &o,student s);
friend istream& operator>>(istream &in,student &s);
};
ostream& operator<<(ostream &o,student s){ //<< 本身是一个函数 返回值是其本身 返回的是引用
cout << s.name << "<-->" << s.score <<endl;
return o;
} //对 输入流运算符 输出流运算符 进行重载
istream& operator>>(istream &in,student &s){ //>> 本身是一个函数 返回值是其本身 返回的是引用
in >> s.name >> s,.score;
return in;
};
student stu_1;
cin >> stu_1; //operator(cin,stu_1) 调用后值是 cpoy 故使用引用地址
cout << stu_1; //偌未重新定义cout 函数(对cout增加功能) 输出流运算符无法识别 实际过程 此函数相当于 operator(cout,stu_1)
运算符重载 C++有很多运算符可以重载
#if 1
#include<iostream>
class Point{
double x,y; //默认是私有的
public:
Point(double __x,double __x){
x = __x; y = __y;
}
double operator[](int i) const{ //下标运算符定义 取值 下标运算符必须定义再类内 为成员函数 cout函数 说明不会对私有属性 x,y 修改 从而使函数签名不同
if (i == 0) return x;
else if (i == 1) return y;
else thorw"下标失误"; //抛出异常
}
double& operator[](int i){ //返回的是引用变量
if (i == 0) return x;
else if (i == 1) return y;
else thorw"下标失误"; //抛出异常
}
Point operator+(const Point p){ //由于是内部(成员)函数 故调用时 会自动传递一个掉用的对象的参数 this
return Point(*x + q[0],y + q[1]);
} //则 调用时 p + q 实际为 p.operator+(q)
friend ostream& operator<<(ostream &o,Point p);
friend istream& operator>>(istream &in,Point &p);
};
ostream& operator<<(ostream &o,Point p){
cout << "(" << p.x << "," << p.y << ")" <<endl
return o;
}
istream& operator>>(istream &i,Point &p){
i >> p.x >> p.y
return i;
}
#if 0
Point operator+(const Point p,const Point p){
return Point(p[0]+q[0],p[1]+q[1]);
} //可以放到类内 作为成员函数 此调用 q + p 实际上为 operator(p,q)
#endif
int main(){
Point p(2.2,3.3);
cout << p[0] << " " << p[1]; //p[i] 实际掉用 成员函数 p.operator[](int i)
//p[1] 返回的不是可修改的值 是从x复制来的值 若改为引用则可以
p[1] = 1.2
return 0;
}
#endif
String类、拷贝函数、析构函数
拷贝函数 自定义类型 复制时调用
析构函数 自定义类型 释放内存时调用 即使释放内存
class String{
private:
char *data; //C风格的字符串
int n;
public :
~String() { //析构函数 释放内存 后到先销毁
if(data) delete[] data;
}
#if 0
String (const String &s){ //硬拷贝
data = s.data; //两者一样 指针一样 指向同一块内存 故修改一个另一个跟着改变 一个内存被释放另一个也会如此
n = s.n;
}
#endif
String (const String &s){ //构造函数先来后到
data = new char[s.n + 1]; //指向一块新的动态内存 使两者互不干扰
n = s.n;
for(int i = 0;i < n;i++) //i <= n 将结束字符copy进入 无则自行加入
data[i] = s.data[i];
data[n] = '\0';
}
String(const char *s = 0){
if(s = 0) {data = 0;n = 0;}
else {while (*p) p++;} //结束字符就是0
n = p - s;
data = new char[n+1]; //动态内存分配
for(int i = 0;i <= n;i++) //i <= n 将结束字符copy进入 无则自行加入
data[i] = s[i];
"data[n] = '\0';
}
int size() {return n;}
char operator[](int i) const{
if(i < 0 || i >= n) throw"下标错误";
else return date[i];
}
char& operator[](int i){
if(i < 0 || i >= n) throw"下标错误";
else return date[i];
}
friend ostream& operator<<(ostream &o,String s);
};
ostream& operator<<(ostream &o,String s){
for(int i = 0;i < s.size();i++)
cout << s[i];
return o;
}
String sa = sb; //= 实际上调用了copy构造函数 未定义定义是编译器自定生产硬copy函数
//一个函数结束时会释放 程序内的 堆栈区内存
C/C++ 一般函数 传值为 copy 方式
类模板
vector 是C++中的向量 类模板
自定义Vector 把类型泛化 相应类型 改为模板类型参数 使得统用
struct Point{
int x;
int y;
Point(int tx,int ty) {x = tx;y = ty;}
friend ostream& operator<<(ostream &o,Point p)
};
ostream& operator<<(ostream &o,Point p){
cout << "(" << p.x << "," << p.y << ")" <<endl
return o;
//类模板 用来生成类的
template<typename T> //把存储 需要改动的数据类型改为 T
class Vector{
T *data; //指向动态内存
int capacity; //空间容量
int n;
public:
Vector(int cap = 5){
data = new T[cap];
if(data == 0){
cap = 0;n = 0;break
}
capcity = cap;
n = 0;
}
void push_back(T e){
if(n = capacity){ //空间已满
T *p = new T[2*capacity];
if (p){
for(int i = 0;i < n;i++)
p[i] = data[i]; //将数据复制到 p 所指的新空间中
delete[] data;
data = p;
capacity = 2*capacity;
}
}
data[n] = e;
n++;
}
T operator[](int i) const{
}
T& operator[](int i){
if(Ti < 0||i >= n) throw"错误下标";
return data[i];
}
int size(){
return n;
}
};
Vecter<double> v; //类模板 实例化生成一个对应类型的类
Vexter<student> v
C++补充
命名空间 namespace
使用
- 权限式 (限定)
std::xxx
using std::xxx
指定单个导入using namespace std
全部导入
全剧命名空间
::xxx
orGlobal::xxx
风格 ?导入 ?
命名空间改 namespace stco = std::count
命名空间 不同文件可以相同 可以进行 函数重载
- 优先选择近的
using Base::fun
使内部空间(子类可以试用 fun)- 可以使用匿名名字空间
与 C 兼容 extern 'C'
常量部分
时间维度
- 与编译期
- 编译期
- 运行期
目标为度
- 对象(变量)
- 函数
/***
* : 如何优雅的使用常量
* : 由 #define const constexpr
*/
// 宏 #define 为预编译期的 真常量
// 名字空间 + 使用 using const using 防止写库时入侵 会通过 指针 使用 修改
// const readonly
// constexper 常量表达式 编译期
//头文件使用
模板元编程 ===> constexper
编译器 在编译期 进行 编译器处理
template <int i> struct A {}
template <> struct A<0> {}
template <int>
struct A {
enum {v = A<n-1>::v};
};
template <int base, int exp>
struct Pow{
enum {v = base * Pow<base, exp-1>::v};
}; // 递归调用
template <int base>
struct Pow<base, 1>{
enum {v = base};
}; // 模板偏特化 递归终止条件 模板特化
constexper 使模板元编程比较方便 使编译器实现取得数值
constexper 是 inline
constexpr auto fact(int n) {
if (n == 1) return n;
return n * fact(n-1);
} // constexper 修饰进行 使编译器计算
// 使用
constexpr auto a = fact(8); // 强制编译器执行
// 也可以正常使用
- 模板元编程 性能更高
- constexpr if 语句 尽量不用递归 C++14 以上
- 两者的底层实现不同
- constexpr 使 模板元编程更方便 constexpr if 语句 更方便 C17
编译期 不应有运行时的东西