/* C++ 入门 */
//《Effective C++》《C++ Primer》《STL 源码剖析》
#include <stdio.h>
#include <stdlib.h>
#include <iostream> // 输入输出流
#include <vector>
#include <string>
#include "../test/lesson/test01/test01.h"
#define N 10
#define ADD(a, b) ((a) + (b))
/* void Func1(int a = 1, int b = 2); */
typedef struct SeqList
{
int a[10];
int size;
int capacity;
} SeqList;
// 升级成了类
struct SeqList1
{
// 成员函数
int& at(int i)
{
return a[i];
}
int& operator[](int i) // 运算符重载
{
return a[i];
}
// 成员变量
int a[N];
};
// C 的接口设计
/*
int SLAT(SeqList *ps, int i)
{
return ps -> a[i];
}
void SLModify(SeqList *ps, int i, int x)
{
ps -> a[i] = x;
}
*/
// C++ 接口设计
int& SLAT(SeqList& ps, int i)
{
return (ps.a[i]);
}
typedef struct ListNode
{
int data;
struct ListNode *next;
} ListNode;
void PushBack(ListNode *& pHead, int x)
{
ListNode *newNode = (ListNode *)malloc(sizeof(ListNode));
newNode -> next = NULL;
newNode -> data = x;
if (pHead == NULL)
{
pHead = newNode;
}
else
{
while (pHead -> next != NULL)
{
pHead = pHead -> next;
}
pHead -> next = newNode;
}
}
namespace wjl // 自建命名空间
{
// 命名空间中可以定义变量/函数/类型
int rand = 0;
int Add(int left, int right)
{
return left + right;
}
int Add(int a, int b, int c)
{
return a + b + c;
}
struct Node
{
struct Node *next;
int val;
};
typedef struct Stack
{
int *a;
int top;
int capacity;
} ST;
void StackInit(ST *ps, int n = 4)
{
ps -> a = (int *)malloc(sizeof(n));// 半缺省参数:不给值默认开 4 个
ps -> top = 0;
ps -> capacity = 0;
}
void StackPush(ST *ps, int x)
{
// ...
}
namespace xxx // 套娃
{
int rand = 1;
}
}
using namespace std;// 这一行是干什么的?
// C 语言面临命名冲突的问题,比如引用的库文件和自己命名的变量名一致
// using namespace xxx -> 全部展开命名空间,以后就不用使用 xxx 的域作用限定了。
// 但是可能会造成变量名不明确的问题,大型的项目不要全部展开
// std 是 C++ 标准库的命名空间
using wjl::Add;// 部分展开,默认使用 wjl 命名空间下的 Add
/*
int Add(int left, int right)
{
return (left + right) * 10;
}
*/
// 部分展开会和默认空间内的同名函数产生冲突,所以有时不能部分展开
// 全缺省参数
void Func1(int a = 1, int b = 2) // a = 1, b = 2
{
cout << a + b << endl;
}
// 半缺省参数 - 必须从右往左给缺省值
void Func2(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int Add0(int a, int b)
{
cout << "int Add0(int a, int b)" << endl;
return a + b;
}
int Add0(int a)
{
cout << "int Add0(int a)" << endl;
return a;
}
double Add0(double a, double b)
{
cout << "double Add0(double a, double b)" << endl;
return a + b;
}
void Add1(char a, int b)
{
cout << "void Add1(char a, int b)" << endl;
}
void Add1(int b, char a)
{
cout << "void Add1(char a, int b)" << endl;
}
// 构成重载,调用存在歧义
void Add2(int a)
{
cout << "void Add2(int a)" << endl;
}
void Add2(int a, int b = 1)
{
cout << "void Add2(int a, int b = 1)" << endl;
}
void my_swap(int &a, int &b)
{
int tmp = a;
a = b;
b = tmp;
}
int Count1()
{
int n1 = 0;
n1++;
return n1;
}
int& Count2()
{
int n = 0;
n++;
return n;
}
int func()
{
int a = 1;
return a;
}
inline int add(int x, int y)
{
return x + y;
}
void fu(int)
{
cout << "void fu(int)" << endl;
}
void fu(int *)
{
cout << "void fu(int *)" << endl;
}
int main()
{
printf("hello world\n");
printf("%d\n", wjl::rand);// 域作用限定符 :: 指定命名空间
// 只会到命名空间中找变量
printf("%d\n", wjl::Add(1, 2));// 默认情况下只在全局查找,不会在命名空间中查找
printf("%d\n", Add(1, 2));
printf("%d\n", wjl::xxx::rand);
struct wjl::Node node;// 在 Node 前面加命名空间
// 命名空间会自动合并,在不同的文件中可以定义相同的命名空间
{
// << 流插入运算符 -> 输出
cout << "hello world" << endl;// endl 换行符
// >> 流提取操作符 -> 输入
int i = 0;
cin >> i;
// 好处:可以自动识别类型
int k;
double j;
cin >> k >> j;
cout << k << endl;
cout << j << endl;
cout << &j << endl;
printf("%.1lf\n", j);// 控制位数
}
// 缺省参数调用 - 备胎
{
Func1(2, 3);// 如果传参,那么以传递的参数为准
Func1(3);// 传一个参数 - a
Func1();// 如果不传参,那么以函数的形参值为准
// 显式传参,必须从左往右连续传参,不能直接传后面的参数
Func2(2);
wjl::ST st1;
wjl::StackInit(&st1, 10);// 知道给多少
for (size_t i = 0; i < 10; i++) {
wjl::StackPush(&st1, i);
}
wjl::ST st2;
wjl::StackInit(&st2, 100);
for (size_t i = 0; i < 100; i++) {
wjl::StackPush(&st2, i);
}
wjl::ST st3;
wjl::StackInit(&st3);// 不知道给多少
// 缺省参数不能在声明和定义中同时给出,不然会报错 - 重定义
// 声明给定义不给
}
{
// 函数的重载
// C 语言不允许同名函数
// C++ 可以,但是要求构成函数重载
// 要求函数名相同,参数不同 - 类型不同,个数不同,顺序不同
cout << Add0(1, 2) << endl;
cout << Add0(1.1, 2.2) << endl;
cout << Add0(0) << endl;
// 会自动调用相应的函数(自动识别类型)
Add1('c', 2);
Add1(1, 'c');
// 顺序不同 - 参数的类型顺序不同
// 返回值不同,不能构成重载
// 不在同一个命名空间(域)中,不构成重载
Add2(1, 2);
/* Add2(1); */ // error - 调用歧义
// 那么编译器是如何判断出要使用哪个函数的呢? - 函数名修饰规则
// 在符号表中,两个重载的函数名被加上不同的前后缀,就可以正常调用了
// 前后缀的名字与参数的个数,顺序,类型有关
// 函数名修饰规则带入返回值,那么返回值不同能不能构成重载?
// 不能,因为调用的时候不知道调用哪个函数
}
{
// 引用 - 给一块空间(变量)取一个别名
int a = 0;
int &b = a;// & 在类型和变量的中间
cout << &a << endl;
cout << &b << endl;// 取地址
// 都是 0x7ffffcc24 - 引用变量和它引用的变量共享同一块内存空间
a++;
b++;
cout << a << endl;// 2
cout << b << endl;// 2
// 应用场景:引用传参 - 函数的参数是引用变量
// 好处:形参是实参的别名,共用同一块内存空间,不需要取地址和解引用
// 举例:swap 函数
int c = 5;
my_swap(a, c);
cout << a << endl;// 5
cout << c << endl;// 2
// 链表
ListNode *head = NULL;
PushBack(head, 1);
PushBack(head, 2);
PushBack(head, 3);
PushBack(head, 4);
// 引用的特性
// 1.必须在定义的地方初始化
/* int& b; */ // err
// 2.一个变量可以有多个引用,但是引用在初始化之后不能改变对象
int &d = a;
int &e = a;
int &dd = d;// 给外号取外号
int x = 1;
d = x;// 赋值,而不是改变指向
// 3.对函数使用引用传参时,临时变量只能传给常引用
// 引用做返回值
int ret1 = Count1();// 传值返回:n 给临时变量(寄存器),临时变量给 ret
/* int ret2 = Count2(); */ // 传引用返回 - 临时变量会出现段错误
/* int& ret2 = Count2(); */ // err
// 如果出了函数作用域,则不能使用传引用返回
// 传引用传参的作用:1.提高效率 2.输出型参数(形参的修改影响实参)
// 传引用返回(出了函数作用于对象还在才可以用):1.提高效率 2.修改返回对象
SeqList s;
s.size = 3;
SLAT(s, 0) = 10;// 如果传的不是引用,那么传递的就是临时变量,具有常性,不可更改
SLAT(s, 1) = 20;
SLAT(s, 2) = 30;// 返回引用可以直接赋值修改
cout << SLAT(s, 0) << endl;
cout << SLAT(s, 1) << endl;
cout << SLAT(s, 2) << endl;// 也可以直接打印
// 常引用
const int a0 = 0;
/* int &b0 = a0; */ // err, 权限的放大
// 权限可以平移,可以缩小,不能放大
const int &c0 = a0;// 权限的平移
int x0 = 0;
const int &y = x0;// 权限的缩小
int b0 = a0;// 赋值拷贝,b 修改不影响 a,可以
int i = 0;
/* double &d0 = i; */ // err
const double &d0 = i;
// 类型转换/返回值会产生临时变量,临时变量具有常性 - 权限放大
int ret = func();
/* int &ret0 = func(); */ // err
const int &ret0 = func();// 权限平移才可以
// 指针和引用的区别
// 在底层的汇编代码没有区别,说明在底层没有引用,只有指针
// 引用 - 化了妆的指针
// 不同点:没有多级引用,没有 NULL 引用,...
}
{
// 类
SeqList1 s1;
for (size_t i = 0; i < N; i++)
{
// s1.at(i) = i;
s1[i] = i;
// s1.operator[](i) = i;
}
for (size_t i = 0; i < N; i++)
{
cout << s1[i] << " ";
}
cout << endl;
}
{
// 内联函数 - 针对于宏函数
// 宏的优点:直接被替换,没有类型的严格限制,对于调用较多的函数来说不需要频繁的建立栈帧,节省时间
// 缺点:宏函数容易写错,语法坑很多,不能调试,没有类型安全的检查
cout << ADD(1, 2) << endl;
// 内联函数 inline 会在 release 优化的情况下在调用的地方直接展开,而不会再建立栈帧
cout << add(1, 2) << endl;
// 内联函数在函数较长或者递归的情况下不会被展开 - 编译器可以忽略内联请求
// 防止代码膨胀
// 内联函数不能声明和定义分离(在两个文件中),否则会出现链接错误
f(1);// 这里调用的时候本来应该出现链接错误,但是 CLion 比较智能
// 因为在编译阶段,内联函数不会进入符号表中(因为会被直接展开),没有地址
fx();// 调用的 fx 函数,调用 f 的时候不用走符号表,不需要链接,在原地直接展开
// 解决方案:内联函数不要声明和定义分离 - 直接在 .h 文件中 inline
// 因为内联函数不进入符号表,因此不会出现重复定义
}
{
// auto 关键字 - 自动推导
int a = 0;
auto b = a;// b 也是 int
auto c = &a;
auto &d = a;
// 普通场景没有什么价值
// 类型很长,就有价值 - 简化代码
vector<string> v;
// vector<string>::iterator it = v.begin();
auto it = v.begin();
// auto 不能做函数的参数,也不能做返回值,也不能定义数组
// 查看参数的实际类型:typeid().name()
cout << typeid(it).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
}
{
// 范围 for
int arr[] = {1, 2, 3, 4, 5};
for (auto x: arr)
{
x *= 2;// x 的改变不会印象数组的值
}
for (auto e: arr)
// 依次取数组中的数据赋值给 e
// 自动判断结束,自动迭代(++)
{
cout << e << " ";
}
for (auto &x: arr)
{
x *= 2;// x 是引用,可以直接改
}
for (auto x: arr)
{
cout << x << " ";
}
cout << endl;
// 范围 for 必须用数组名(函数形参不行)
}
{
// nullptr 空指针 - 解决 NULL 的二义性
// 形参可以只写类型
fu(0);
/* fu(NULL); */ // err 不知道调用哪个 - NULL 具有二义性
fu((int *)NULL);
fu(nullptr);// 新关键字
}
return 0;
}
C++ 入门
最新推荐文章于 2024-06-15 16:46:40 发布