EP10
EP35
EP37
数字反转:
int reverse_num(int n) {
int temp = 0;
while(n) {
temp = temp * 10 + n % 10;
n /= 10;
}
return temp;
}
取数位,如果未知数字长度,可以定义char *字符串
然后遍历输出字符。
结构体(数据类型):定义变量、数组。
struct person {
char name[20];
int age;
char gender;
float height;
};
struct关键字是C语言特有的
struct在C++中的是类
C语言中定义:struct person Yu;
匿名结构体:
匿名结构体只能使用1次
// 匿名结构体
struct {
char name[20];
int age;
char gender;
float height;
};
访问成员:
直接引用 . Yu.name
间接引用 -> p = &Yu p->name
结构体申请空间的对齐方式:
根据定义顺序已知最长的长度的倍数来决定最终的占内存大小。
对齐方式是根据相应成员的类型,与其倍数的地址对齐。不够的补空白。
name占用20字节[0-19]
age占用4字节[20-23]
gender占用1字节[24],其中[25-27]是空的
height占用4字节[28-31]
#include <stdio.h>
struct node1 {
char a;
char b;
int c;
};
struct node2 {
char a;
int c;
char b;
};
int main() {
printf("node1 = %d\n", sizeof(node1));
printf("node2 = %d\n", sizeof(node2));
return 0;
}
node1内存占用:
char a占[0]
char b占[1]
[2-3]空位
int c占[4-7]
node1占8B
node2内存占用:
char a占[0]
[1-3]空位
int c占[4-7]
char b 占[8]
[9-11]空位
node2占12B
相同类型放一起声明,会更节约空间。
-----------------------------------------------
共用体(数据类型):定义变量、数组。
若干变量共用同一片内存
关键字 union
union register {
struct {
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
} bytes;
unsigned int number;
};
里面有两个基本字段:
匿名结构体 定义的 变量bytes 占4B
无符号整形 number 占4B
其内存形式:
[0 bytes(byte1)][0 number]
[1 bytes(byte2)][1 ]
[2 bytes(byte3)][2 ]
[3 bytes(byte4)][3 ]
共用体哪个字段的大,则内存有多大
访问形式同结构体:直接引用、间接引用。
union node {
double a;
char b;
int c;
};
其内存占用结构图;
[0 double][0 char][0 int]
[1 ][ ][1 ]
[2 ][ ][2 ]
[3 ][ ][3 ]
[4 ][ ][ ]
[5 ][ ][ ]
[6 ][ ][ ]
[7 ][ ][ ]
[8 ][ ][ ]
如果8个字节都取了,则当为a变量
只取1B,则是b变量
连续取4B,则是c变量
如果:
union node {
double a;
char b[20];
int c;
};
则占24B,因为最大的是double,如果去掉double a,则占20B
随堂练习:
使用共用体,实现ip转整数的功能。
用4B表示ip,需要unsigned int
uint_8 用%hhd
my_code:
#include <stdio.h>
union ip {
unsigned char arr[4];
unsigned int a;
};
int main() {
union ip ip1;
scanf("%hhd.%hhd.%hhd.%hhd", &ip1.arr[0], &ip1.arr[1], &ip1.arr[2], &ip1.arr[3]);
printf("%u\n", ip1.a);
return 0;
}
船长code:
#include <stdio.h>
union IP {
struct {
unsigned char a1;
unsigned char a2;
unsigned char a3;
unsigned char a4;
} ip;
unsigned int num;
};
int main() {
union IP p;
char str[100] = {}; // 接收输入的ip地址字符串
while (~scanf("%s", str)) {
sscanf(str, "%hhd.%hhd.%hhd.%hhd", &p.ip.a1, &p.ip.a2, &p.ip.a3, &p.ip.a4);
printf("%u", p.num);
}
return 0;
}
此处可看大端小端,目前主流存储方式都是小端机。大端是历史电脑机
小端:数字的低位->低地址位
大端:数字的低位->高地址位
大端机数据存储:
高位 低位
[192][168][0][1]
0 1 2 3
低地址 高地址
小端机数据存储:
低位 高位
[1][0][168][192]
0 1 2 3
低地址 高地址
证明自己是小端机:
是小端机输出1
存一个int 1 看看在4B中的低地址还是高地址
is_litle() {
int num = 1;
return ((char *)(&num))[0];
}
/*
0是低地址
3是高地址
*/
字节序:
①主机字节序(大小端问题)/本地字节序
socket需要本地字节序转换成网络字节序
②网络字节序
------------------------------------------------
enum 枚举型,相当于符号常量
有enum weekday {Sun, Mon, Tue, Wed, Thu, Fri, Sar} day1, day2;
day1 > Sat 等价于 day1 > 6
-----------------------------------------------
OJ203:
快速排序:
线性筛:一套框架算法
EP07题:找第10001个素数
估算:开10000 * 20大小的数组,筛选。
素数筛O(loglogN)速度还慢,线性筛O(N)更快
#include <stdio.h>
#define MAX_N 200000
void is_prime(int *arr) {
for (int i = 2; i < MAX_N; i++) {
if (arr[i]) continue;
arr[++arr[0]] = i;
for (int j = i; j < MAX_N / i; j++) {
arr[j * i] = 1;
}
}
return ;
}
int main() {
int arr[MAX_N] = {0};
is_prime(arr);
printf("%d\n", arr[10001]);
return 0;
}
如果一个数字N的素数因式分解中有m种不同的素数,N被标记了m次
而线性筛只被标记1次。速度更快。
标记->数组
作用:筛选一定范围内所有的素数。
核心思想:用整数 i
* prime[j]
(小于 i
最小素因数的素数表)去 数组 合数 prime[j] * i
。(PS: 素数筛的 i
是素数,这里的 i
是素数+合数)
prime[j] * i
和 i
有如下性质:prime[j] * i
中最小的素数为 prime[j]
prime[j] * i
可以表示成为 prime[j] * i
prime[j]
一定 <= i
中最小的素因子i * prime[j]
(所有不大于 i
中最小素数的集合,如 2,3,5... <= i
的最小素数)数组 prime[j] * i
判断 i
的最小素数的方法:如果 i % prime[j] == 0
,那么 prime[j]
即为 i
的最小素数。
由 ①② 得:prime[j] * i = prime[j] * i
i
是除了 prime[j] * i
以外的最大因数。
③ 保证了 i
是最大因数
如 prime[j] * i
= 30:
prime[j] * i = prime[j] (2) * i (15)
i
的最小素因数为 3 (3*5 = 15)如果 prime[j] = 3
,则 i = 10
,此时 i
的最小素因数为 2,不满足条件 ③。
④ 表示数组,i
从 2 开始,prime[j]
依次取值 2,3,5... 依次数组 prime[j] * i
例如 数组 prime[j] * i = 30
:
i = 15
时,才会数组 prime[j] * i = 30
。被数组的 prime[j] * i = i * prime[j]
prime[j] * i |
i |
prime[j] |
---|---|---|
30 | 15 | 2 |
8 | 4 | 2 |
45 | 15 | 3 |
i
对应可数组的 prime[j] * i
:
i=4
,可以数组的 prime[j] * i
:4×2 = 8i=25
,可以数组的 prime[j] * i
:25×2, 25×3, 25×5i=45
,可以数组的 prime[j] * i
:45×2, 45×3i
等于:45
#include <stdio.h>
#define MAX_N 100
// 素数0 合数1
// 被标记的N = 标记M * P(素数集合)
void linnear_init(int *arr) {
for (int i = 2; i <= MAX_N; i++) {
if (!arr[i]) arr[++arr[0]] = i;
for (int j = 1; j <= arr[0] && arr[j] * i <= MAX_N; j++) {
arr[arr[j] * i] = 1;
if (!(i % arr[j])) break;
}
}
}
void su_prime(int *arr) {
for (int i = 2; i < MAX_N; i++) {
if (!arr[i]) arr[++arr[0]] = i;
for (int j = i; j <= MAX_N / i; j++) {
arr[j * i] = 1;
}
}
return ;
}
int main() {
int arr[MAX_N] = {0};
// su_prime(arr);
xian_init(arr);
for (int i = 1; i <= arr[0]; i++) {
printf("%d\n", arr[i]);
}
return 0;
}
比线性筛更快的判断素数方式:
罗宾米勒测试,有误差(很低),需要数论的知识。
-----------------------------------------------
线性筛提升为算法框架(重要)
求100以内所有数字的因素的个数
F[MAX_N]记录因素个数
先写一个线性筛。 然后改
素数的因子个数肯定为2(1和本身)
F[M] = 2;
M,P互素的话,F(M * P)= F(M) * F(P)
if 互素 F[M * arr[P]] = F[M] * F[arr[P]]
else 不互素(M % arr[p] == 0)
任何的N都可以写成 素因子次幂 连乘 Pi ^ ai
cnt[MAX_N]记录次幂
F[M * arr[P]] = F[M] / (cnt[M] +1) * (cnt[M] + 2)
cnt[M * arr[P]] = cnt[M] +1
作业:求解因子和问题
二分查找:O(log 2 N)
基础的二分查找只管是否存在,不管是第几个。
算法提升:
在0000000001111111111 找第一个1
找最先满足的要求的位置
int binnary_search1(int *arr, int n) {
int head = 0, tail = n, mid; // n是虚拟位
while (head < tail) {
mid = (head + tail) >> 1;
if (arr[mid] == 0) head = mid + 1;
else tail = mid; // 这个1可能就是第一个1,所以不能mid-1
}
return head == n ? -1 : head; //如果头指向虚拟位,则没找到
}
在1111111111000000000 找最后一个1
找最后一个满足要求的位置
int binnary_search2(int *arr, int n) {
int head = -1, tail = n - 1, mid; // -1是虚拟位
while (head < tail) {
mid = (head + tail + 1) >> 1; // +1是为了上取整
if (arr[mid] == 0) tail = mid - 1;
else head = mid; // 这个1可能就是最后一个1,所以不能mid+1
}
return head; //如果尾指向虚拟位,则没找到
}
oj195
#include <cstdio>
int binary_search(int *arr, int n, int x) {
int min = 0, max = n - 1, mid;
while (min < max) {
mid = (min + max + 1) >> 1;
if (arr[mid] > x) max = mid - 1;
else min = mid;
}
return arr[min];
}
int main() {
int n, m, x;
scanf("%d%d", &n, &m);
int *nrr = new int[n]();
for (int i = 0; i < n; i++) {
scanf("%d", &nrr[i]);
}
for (int i = 0; i < m; i++) {
scanf("%d", &x);
i && printf(" ");
printf("%d", binary_search(nrr, n, x));
}
delete(nrr);
return 0;
}
二分查找实现开平方根(sqrt):
连续问题,离散问题
此问题属于连续问题:
#include <stdio.h>
#include <math.h>
double my_sqrt(double x) {
double min = 0, max = x + 1.0, mid;
#define EPSL 1e-7
while (max - min > EPSL) {
mid = (max + min) / 2.0;
if (mid * mid < x) min = mid;
else max = mid;
}
#undef EPSL
return mid;
}
int main() {
double x;
while (~scanf("%lf", &x)) {
printf("sqrt(%g) = %g\n", x, sqrt(x));
printf("my_sqrt(%g) = %g\n", x, my_sqrt(x));
}
return 0;
}
%g用于打印浮点型数据时,会去掉多余的零,至多保留六位有效数字(不同于%e的默认保留小数点后6位)
1e-7是10的-7次方
max = x + 1.0是为了:如果开小于1的平方根的话,会导致寻找的数字根本不在范围。所以加上1,即可包含进查找范围
------------------------------------------------
牛顿迭代算法(logN):是C语言sqrt底层实现的算法
牛顿迭代解决高阶(单调)方程求根问题
方程必须连续可导才能用牛顿迭代法
用牛顿迭代设计sqrt:
x = √n
x * x = n
原函数F(x) = x * x - n表示x与√n之间的误差值
f(x) = 2x表示斜率,用于求下一个迭代x的值
-----------------------------
如求√5: f(x) = x^2 - 5
随便取一点初始值x0, 取x0 = 5
则点(x0, f(x0))的切线为f'(x0)
切线方程:点斜式y-f(x0)=f'(x0)(x-x0),
其与x轴交于(x1, 0),带入切线方程得:
0-f(x0)=f'(x0)(x1-x0)
解得x1 = x0 - f(x0) / f'(x0)
即为1次迭代,下一次取(x1, f(x1)),取切线方程,交于x轴为x2,然后再取(x2, f(x2))...
直到x * x 与 n之间的差值即F(x, n)的返回值小于误差EPSL,输出其x为平方根。
#include <stdio.h>
#include <math.h>
// 原函数f(x) = x^2 - n, 因为x^2 = n, x=根号n
double F(double x, double n) {
return x * x - n;
}
// 导函数f'(x) = 2x
double f(double x) {
return 2 * x;
}
double NewTon(double (*F)(double, double), double (*f)(double), double n) {
double x = n;
#define EPSL 1e-7
while (fabs(F(x, n)) > EPSL) {
x -= F(x, n) / f(x);
}
#undef EPSL
return x;
}
double my_sqrt(double n) {
return NewTon(F, f, n);
}
int main() {
double n;
while(~scanf("%lf", &n)) {
printf("sqrt(%g) = %g\n", n, sqrt(n));
printf("my_sqrt(%g) = %g\n", n, my_sqrt(n));
}
return 0;
}
PS:最快的sqrt是O(1)的,雷神之锤3的工程师设计的
扩展欧几里得结尾
------------------------------------------------
变参函数:
实现可变参数max_int,从若干个传入的参数中返回最大值。
int max_int(int a, ...);
... 是变参列表,不能通过名字定位变量。
变参函数最起码有一个参数的名字是已知的
需要stdarg.h
如何获得a往后的参数列表?
va_list类型的变量
如何定位a后面一个参数的位置?
va_start(va_list类型变量, 已知名字参数)宏
如何获取下一个va_list中的参数?
while (n--) {
va_arg(va_list类型变量, 数据类型)宏
}
如何结束整个获取可变参列表的动作?
va_end(va_list类型变量)宏
(函数不能传入int数据类型,所以是宏)
#include <stdio.h>
#include <inttypes.h>
#include <stdarg.h>
#define P(func) {\
printf("%s = %d\n", #func, func);\
}
int max_int(int n, ...) {
int ans = INT32_MIN; //inttypes.h中直接获取int的最小值
va_list arg;
va_start(arg, n);
while (n--) {
int tmp = va_arg(arg, int);
if (tmp > ans) ans = tmp;
}
va_end(arg);
return ans;
}
int main() {
P(max_int(3, 1, 2, 3));
P(max_int(5, 1, 3, 5, 23, 4));
P(max_int(4, 3, 1, 2, 5, 123, 1245));
return 0;
}
------------------------------------------------
实现简版的printf() 函数:
首先能输出: putchar() 每次向屏幕当中打印1个字符 (与getchar() 是一家)。printf底层是putchar封装的。
printf支持的操作:
①打印一个字符串
②取参数%d %c %s等等
#include <stdio.h>
#include <stdarg.h>
#include <inttypes.h>
#define swap(a, b) a^=b, b^=a, a^=b
int my_printf(const char *frm, ...) {
int cnt = 0;
va_list arg;
va_start(arg, frm);
#define PUTC(x) putchar(x), ++cnt
for (int i = 0; frm[i]; i++) {
switch (frm[i]) {
case '%': {
switch (frm[++i]) {
case '%': PUTC(frm[i]); break;
case 'c': {
uint8_t ch = va_arg(arg, int);
PUTC(ch);
} break;
case 'd': {
int tmp = va_arg(arg, int);
uint32_t x;
if (tmp < 0) PUTC('-'), x = -tmp;
else x = tmp;
int arr[11] = {'0'};
int dig = 0;
for (int i = 0; x || x % 10; i++, x /= 10) {
arr[i] = x % 10 + '0', dig++;
}
for (int i = 0; i < dig / 2; i++) {
swap(arr[i], arr[dig - 1 - i]);
}
for (int i = 0; arr[i]; i++) {
PUTC(arr[i]);
}
} break;
case 's': {
const char *str = va_arg(arg, const char *);
for (int i = 0; str[i]; i++) {
PUTC(str[i]);
}
} break;
}
} break;
default: PUTC(frm[i]); // 直接输出%
}
}
#undef PUTC
va_end(arg);
return cnt;
}
int main() {
my_printf("hello world\n");
my_printf("int (123) = %d\n", 123);
my_printf("int (0) = %d\n", 0);
my_printf("int (1000) = %d\n", 1000);
my_printf("int (-123) = %d\n", -123);
my_printf("INT32_MAX = %d\n", INT32_MAX);
my_printf("INT32_MIN = %d\n", INT32_MIN);
my_printf("test_str = %s\n", "FUCK!");
my_printf("int (190000001) = %d\n", 190000001);
my_printf("char (%%) = %c\n", '%');
return 0;
}
数组与预处理:
函数内部定义的数组,是在栈区(并不会清空内存值),不初始化的话内部会有脏数据;
memset也能初始化数组。
数组性质:
int arr[100]静态数组,空间连续。
支持随机访问(下标访问)。
数组是相同变量的集合;
数组的地址,arr就是地址,也是&arr[0];
初始化int arr[2] = {5, 8};
数组通常表示两个信息:下标值和值
还有值的正负,三个信息。
-----------------------------------------------
素数筛:用素数标记掉合数
标记一个范围内的数字是否为合数,没有标记的则为素数。
空间复杂度O(N),时间复杂度O(N * loglogN)(无限接近于O(N))
总体思想是用素数标记掉不是素数的数字,例如先知道了i是素数,则2i、3i、4i... ... 就都不是素数。
暴力算法:从2~N-1找i,能被N整除,就是合数。O(N)
for (int i = 2; i < n; i++) {
if (n % i == 0) return 0;
}
return 1;
优化为O(sqrt(N)) :
for (int i = 2; i * i <= n; i++) {
if (n % i == 0) return 0;
}
return 1;
但是如果遍历的话,嵌套了一层循环,时间复杂度变为O(N * sqrt(N)) ,不是很好。
------------------------------------------------
素数筛步骤:(默认都为0,标记合数为1)
①用prime[i]来标记i是否为合数
②标记为1的数字为合数,否则为素数,如:
prime[2] = 0表示素数
prime[4] = 1表示合数
③第一次知道2是素数1,则将2的倍数标记为1;
④向后找到第一个没有被标记的数字i
⑤将i的倍数全部标记为合数
⑥重复4~6步,直到标记完范围内的所有数字。
============================
开数组一般多开大一点,防止数组越界。
如:
#define MAX_N 100
int prime[MAX_N + 5] = {0};
初始化函数:
void init() {
for (int i = 2; i <= MAX; i++) {
if (prime[i]) continue; // 1: not prime, if(1) do nothing
for (int j = 2 * i; j <= MAX; j += i) {
prime[j] = 1;
}
}
}
int main() {
init();
// 遍历输出
for (int i = 2; i < MAX_N; i++) {
if (prime[i]) continue;
cout << prime[i] << endl;
}
return 0;
}
===========================
优化:
void init() {
for (int i = 2; i < MAX_N; i++) {
if (prime[i]) continue; //如果为合数(标记为1的是合数)什么也不干
prime[++prime[0]] = i; //用prime[0]记录素数的个数,然后从prime[1]开始记录第一个素数,方便后续遍历用
for (int j = 2 * i; j < MAX_N; j += i) {
prime[j] = 1;
}
}
return ;
}
++prime[0] 统计个数兼下标。
===========================
再优化:j = 2 * i;变成 i * i(减少一部分向下枚举的重复标记,正常思维肯定是向上枚举素数)
for (int j = i * i; j < MAX_N; j += i) {
prime[j] == 1;
}
上面代码有问题 i * i很有可能导致int溢出
不建议int改long long,因为int的计算速度比long long运算速度快
所以好的方式是不变int,乘法改除法:
for (int j = i; j < MAX_N / i; j++) {
prime[j * i] = 1;
}
===========================
素数筛的框架思想能解决的问题:
100以内每个数字最小的素因子。
素因子:素数的因子,比如6的因子:1, 2, 3, 6;其中最小素因子为2。
#define MAX_N 100;
int prime[MAX_N + 5] = {0};
void init() {
for (int i = 2; i <= MAX_N; i++) {
if (prime[i]) continue;
for (int j = i; j <= MAX_N; j += i) {
if (prime[j]) continue; // 求最大素因子注释掉此行
prime[j] = i;
}
}
return ;
}
int main() {
init();
for (int i = 2; i <= MAX_N; i++) {
printf("min_fac[%d] = %d\n", i, prime[i]);
}
return 0;
}
HZOJ:191题目
数组作为函数参数在讲预处理里面
OJ165结尾
--------------------------------------------------
欧几里得算法:
辗转相除法,用于快速计算两个数字的最大公约数。
还可用于快速求解 a*x + b*y = 1方程的一组整数解
gcd(a, b) a与b两个数最大公约数的值。
gcd(a, b) => gcd(b, a % b)
大问题缩小成小问题,直到某一层的b值为0,得到答案及a的值。
如何证明?
①假设gcd(a, b) = r, 证在b和a%b中也包含r
设:
a = xr
b = yr 其中x, y∈Z,且x,y互素(gcd(x, y) = 1)
a % b = a - kb , k∈Z,k = a // b (//下取整)
= xr - kyr
= r(x - ky)
② 证r 为最大
其中:
b = yr
a % b = r(x - ky),
证明y与x - ky互素即可。
证:gcd(y, x - ky) = 1
令gcd(y, x - ky) = d,其中d恒等于1.
设:
y = md
x - ky = nd , 其中m, n ∈ Z
推导==>
y = md
x = d * (n + km)
又由于gcd(x, y) = 1
所以d只能恒为1
-----------------------------------------------
int gcd(int a, int b) {
if (!b) return a;
return gcd(b, a % b);
}
一行写法:
int gcd(int a, int b) {
return (b ? gcd(b, a % b) : a);
}
最小公倍数:
lcm(a, b) = a*b / gcd(a, b)
设:
a = gcd(a, b) * x
b = gcd(a, b) * y
则:a * b = x * y * gcd^2(a, b)
所以:lcm(a, b) = a*b / gcd(a, b)
int lcm(int a, int b) {
return a / gcd(a, b) * b;
}
最好先除再乘,防止乘爆了int的取值范围。
------------------------------------------------
欧几里得解决二元一次方程组ax + by = 1一组整数值解,a,b,x,y均为整数:
前提a,b互素,即gcd(a, b) = 1
ax + by = 1
推导:
入口: 中间: 尾部边界:
a b b a%b a b
x y x1 y1 x11 y11
中间:b*x1 + (a%b)*y1 = 1
尾部:a*x11 + b*y11 = 1(边界b=0,取x11=1,y11=0)
展开中间:
b*x1 + (a - kb)*y1 = 1
b*x1 + a*y1 - b*ky1 = 1 ,k = a // b (//下取整)
a*y11 + b(x11 - k*y11) = 1
推导至入口:
ax + by =1 ==>
a*y1 + b*(x1 - k*y1) = 1
x是下一层的y
y是下一层的x-ky
扩展欧几里得算法:
ax + by = gcd(a,b)
int ex_gcd(int a, int b, int* x, int* y) {
if (!b) {
*x = 1, *y = 0;
return a;
}
int xx, yy, ret = ex_gcd(b, a%b, &xx, &yy);
*x = yy;
*y = xx - (a%b) * yy;
return ret;
}
思考题:如何去掉xx yy?
int ex_gcd(int a, int b, int* x, int* y) {
if (!b) {
*x = 1, *y = 0;
return a;
}
int ret = ex_gcd(b, a%b, y, x);
*y -= (a / b) * (*x);
return ret;
}
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int count = 0; // 查看递归次数
int ex_gcd(int a, int b, int* x, int* y) {
if (!b) {
*x = 1, *y = 0;
count++;
return a;
}
int xx, yy, ret = ex_gcd(b, a % b, &xx, &yy);
*x = yy;
*y = xx - (a / b) * yy;
count++;
return ret;
}
int ex_gcd1(int a, int b, int* x, int* y) {
if (!b) {
*x = 1, *y = 0;
return a;
}
int ret = ex_gcd1(b, a % b, y, x);
*y -= (a / b) * (*x);
return ret;
}
int main(int argc, char** argv) {
int a, b, x, y;
while (~scanf("%d%d", &a, &b)) {
printf("gcd(%d, %d) = %d\n", a, b, ex_gcd1(a, b, &x, &y));
printf("%d * %d + %d * %d = %d\n", a, x, b, y, ex_gcd(a, b, &x, &y));
printf("count:%d\n", count);
count = 0;
}
return 0;
}
数组的代码演示:
#include <stdio.h>
void func(int* num) {
printf("func: ");
printf("sizeof(num) = %lu\n", sizeof(num));
return ;
}
int main() {
int arr[100] = {}, n;
printf("sizeof(arr) = %lu\n", sizeof(arr));
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", arr + i);
}
func(arr);
return 0;
}
函数里接收的数组,与原数组不是同一个东西,函数里的形参本质上是一个指针
二维数组传参:
void func(int **num) { // int **num是错误的
}
void func(int num[100][20]) {
}
void func(int num[][20]) {
}
void func(int (*num)[20]) {
// int * 指向一个 int [20]
}
int main() {
int arr[100][20] = {};
func(arr);
return 0;
}
int **num = arr; 报警告是因为地址的类型不匹配。
因为:
int arr[100][20] = {0};
int **num = (int **)arr;
arr[%p]与arr+1[%p]相差20*4B的大小,但是
num[%p]与num[%p]相差8B,地址的大小。
因为int *(*num)是跟着类型的。
外在表现形式是不同的。
三维数组传参:
void func(int ***num) { // int ***num是错误的
}
void func(int num[100][20][10]) {
}
void func(int num[][20][10]) {
}
void func(int (*num)[20][10]) {
// int * 指向一个 int [20][10]
// 最常用
}
int main() {
int arr[100][20][10] = {};
func(arr);
return 0;
}
维度至多只能省略第一个维度
--------------------------------------------
宏作用:文本替换,不做计算
定义符号常量:
#define PI 3.1415926
#define MAX_N 10000
定义傻瓜表达式:不需要加 { }
#define MAX(a, b) (a) > (b) ? (a) : (b)
#define S(a, b) a * b
定义代码段:需要加 { }
#define P(a) { \
printf("%d\n", a); \
}
定义代码段时,如果{}外面加了(),即({}),
小括号的作用是:变成表达式,获取返回值
C语言预定义的宏:
__DATA__ 替换成编译时的日期:Mmm dd yyyy。
__TIME__ 替换成编译时的时间:hh:mm:ss
__LINE__ 替换当前代码的行号
__FILE__ 替换成当前.c的文件名
打印函数信息的宏:(以下3个是非标准宏,所有OS并非通用)
__func__ linux标准
__FUNC__ windows标准
__PRETTY_FUNCTION__
还有很多其他的预定义宏需要自己学习提升
------------------------------------------------
条件式编译:(用于代码剪裁,增加可移植性)
#ifdef DEBUG 是否 定义了 DEBUG宏
#ifndef DEBUG 是否 没定义 DEBUG宏
// 用于版本号之类的
#if MAX_N == 5 宏MAX_N是否等于5
#elif MAX_N == 4 否则宏MAX_N是否等于4
#else
#endif 使用条件式编译,末尾必须加#endif
编译期检查:语法分析、词法分析、语义分析
链接:将所有的.o文件整合成a.out
工业开发:多文件“链编”
随堂练习:实现没有BUG版的MAX(a, b)宏返回a b二者中的最大值,需要通过如下测试:
MAX(2, 3) 3
5 + MAX(2, 3) 8
MAX(2, MAX(3, 4)) 4
MAX(2, 3 > 4 ? 3 : 4) 4
MAX(a++, 6) // a的初始值为7,函数返回值为7,a的值变为8
#include <cstdio>
#define MAX(a, b) ({ \
__typeof(a) x, y; \
x = a; y = b; \
(x) > (y) ? x : y; \
})
//#func表示将读入的MAX(2,3)字符串化
#define P(func) { \
printf("%s = %d\n", #func, func); \
}
int main() {
int a = 7;
P(MAX(2, 3));
P(5 + MAX(2, 3));
P(MAX(2, MAX(3, 4)));
P(MAX(2, 3 > 4 ? 3 : 4));
P(MAX(a++, 6));
P(a);
return 0;
}
vim矩形删除:先两下esc 然后Ctrl+V,上下左右选中,最后 dd 删除
vim批量行添加:Ctrl+V,上下左右选中插入的行,然后按大写的I,进行输入,输入结束两下esc
-----------------------------------------------
宏定义其他冷门、重点知识
#define Conn(x,y) x##y
#define ToChar(x) #@x
#define ToString(x) #x
x##y表示什么?表示x连接y,举例说:
int n = Conn(123,456); 结果就是n=123456;
char* str = Conn("asdf","adf")结果就是 str = "asdfadf";
#@x,其实就是给x加上单引号,结果返回是一个constchar。
举例说:
char a = ToChar(1);结果就是a='1';
做个越界试验char a = ToChar(123);结果是a='3';
但是如果你的参数超过四个字符,编译器就给给你报错了!error C2015:too many characters in constant :P
#x是给x加双引号
char* str = ToString(123132);就成了str="123132";
如果有#define FUN(a,b) vo##a##b()那么FUN(idma,in)会被替换成void main()
main函数的参数讲解
int main(int argc, char* argv[], int** env)
#include <stdio.h>
int main(int argc, char* argv[], char** env) {
printf("%d\n", argc);
for (int i = 0; i < argc; i++) {
printf("argv[%d] = %s\n", i, argv[i]);
}
for (int i = 0; env[i]; i++) {
printf("env[%d] = %s\n", i, env[i]);
}
return 0;
}
以下两种方式可以将空格作为字符串传入
./a.out "hello world"
./a.out hello\ world
环境变量字符串数组env[]最后的是NULL表示结尾
----------------------------------------------
工程项目开发:
函数声明和定义
报错:
函数未声明(编译阶段:检查语法、语义)
"xxx" was not declared in this scope
函数未定义(链接阶段:组装拼接)
undefined reference(引用) to "xxx"
error:ld returned 1 exit status(链接阶段错误)
函数重定义(链接阶段):链接的两个.o中某个函数有两个定义。
multiple definition of "xxx"
error:ld returned 1 exit status(链接阶段错误)
区分报错的时期不同
文件main.cpp
#include <stdio.h>
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcB : %d\n", n);
funcB(n - 1);
return ;
}
int main() {
funcA(5);
return 0;
}
文件unite.cpp
#include <stdio.h>
void funcA(int);
void funcB(int n) {
if (n == 0) return ;
printf("funcB : %d\n", n);
funcA(n - 1);
return ;
}
g++ -c main.cpp 生成main.o
g++ -c unite.cpp 生成unite.o
g++ main.o unite.o 生成a.out
------------------------------------------------
头文件(xxx.h)与源文件(xxx.c cc cpp)
头文件放声明;
源文件放定义。
自己写的头文件要用 #include "xxx.h"
<>的头文件,编译器从系统路径下查找头文件。
""的头文件,编译器从当前路径下查找头文件。
头文件中不许写函数定义,否则容易造成重定义,以及头文件包含需要顺序
条件式编译:
#ifndef _xxx_
#define _xxx_
#endif
//如果没定义_xxx_宏
//则定义_xxx_宏
_xxx_最好和头文件一个名字
.o文件可以通过ld命令查看
---------------------------------------------
如果把函数定义写到头文件:
文件main.cpp
#include <stdio.h>
#include "head_c.h"
#include "head_ab.h"
//#include "head_d.h"
int main() {
funcA(5);
funcC(2, 3);
//funcD(3, 4);
return 0;
}
文件head_ab.h
#ifndef _HEAD_AB_H
#define _HEAD_AB_H
void funcA(int);
void funcB(int);
void funcA(int n) {
if (n == 0) return ;
printf("funcA : %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB : %d\n", n);
funcA(n - 1);
return ;
}
#endif
文件head_c.h
#ifndef _HEAD_C_H
#define _HEAD_C_H
#include "head_ab.h"
void funcC(int a, int b) {
printf("FuncC : %d + %d = %d\n", a, b, a + b);
funcA(a);
return ;
}
#endif
以上虽然不会报错,但是如果再添加其他.cc文件
文件head_d.h
#ifndef _HEAD_D_H
#define _HEAD_D_H
void funcD(int, int);
#endif
文件head_d.cc
#include <stdio.h>
#include "head_ab.h"
void funcD(int a, int b) {
printf("funcD : %d + %d = %d\n", a, b, a + b);
funcA(a);
return ;
}
则在head_d.cc中又包含了head_ab.h,造成了函数的重定义,所以函数定义不能放在头文件。
------------------------------------------------
Linux工程项目规范:
根目录:
文件夹include放全部的.h
文件夹src全部放.cc
文件夹bin放exe
文件夹lib放.a静态链接库
main.cpp放在外面
Makefile
头文件:
funcAB.h
#ifndef _FUNC_AB_H
#define _FUNC_AB_H
void funcA(int);
void funcB(int);
#endif
funcC.h
#ifndef _HEAD_C_H
#define _HEAD_C_H
void funcC(int, int);
#endif
funcD.h
#ifndef _FUNC_D_H
#define _FUNC_D_H
void funcD(int, int);
#endif
源文件:
文件main.cpp
#include <cstdio>
#include <funcAB.h>
#include <funcC.h>
#include <funcD.h>
int main() {
funcA(5);
funcC(2, 3);
funcD(3, 4);
return 0;
}
./src/funcAB.cc
#include <funcAB.h>
#include <cstdio>
void funcA(int n) {
if (n == 0) return ;
printf("funcA : %d\n", n);
funcB(n - 1);
return ;
}
void funcB(int n) {
if (n == 0) return ;
printf("funcB : %d\n", n);
funcA(n - 1);
return ;
}
./src/funcC.cc
#include <funcAB.h>
#include <cstdio>
void funcC(int a, int b) {
printf("FuncC : %d + %d = %d\n", a, b, a + b);
funcA(a);
return ;
}
./src/funcD.cc
#include <funcAB.h>
#include <cstdio>
void funcD(int a, int b) {
printf("funcD : %d + %d = %d\n", a, b, a + b);
funcB(b);
return ;
}
如果想将自己的头文件引用" "变成< >,
则改完以后自己在gcc 后加参数-I./include
Makefile工具:方便多文件链编
.PHONY: clean
all: main.o ./src/funcAB.o ./src/funcC.o ./src/funcD.o libtest.a
g++ -I./include -L./lib main.o -ltest -o ./bin/test.exe
./src/funcAB.o: ./include/funcAB.h ./src/funcAB.cc
g++ -I./include -c ./src/funcAB.cc -o ./src/funcAB.o
./src/funcC.o: ./include/funcC.h ./src/funcC.cc
g++ -I./include -c ./src/funcC.cc -o ./src/funcC.o
./src/funcD.o: ./include/funcD.h ./src/funcD.cc
g++ -I./include -c ./src/funcD.cc -o ./src/funcD.o
main.o: main.cpp ./include/*.h
g++ -I./include -c main.cpp
libtest.a: ./src/funcAB.o ./src/funcC.o ./src/funcD.o
ar -r ./lib/libtest.a src/*.o
clean:
rm ./bin/test.exe main.o ./src/*.o ./lib/libtest.a
加.PHONY: clean是防止:如果当前路径下有文件名字为clean,则不影响。功能是创建一个虚拟空间清理。
------------------------------------------------
链接库:
不能直接发同时src和include,因为首先对方要自己写makefile编译链接,其次没有保密性。
头文件可以发,源文件要变成链接库。
动态链接库:压缩的很小,开放权限共享
静态链接库:比较大,一对一的copy
自己搜:不同系统下如何生成静态链接库
以下为linux环境:
ar -r libxxx.a src/*.o 生成了静态链接库
lib是前缀, .a是后缀 ,中间xxx是名字,后续指定库时候的参数是-lxxx
然后:
g++ -I./include -c main.cpp
生成main.o
然后:
g++ -I./include -L./lib main.o -lxxx
注意main.o在前,-lxxx在后
即可调用库生成a.out
函数:
int is_prime(int x) {}
返回值 函数名(参数声明列表) {
return x
}
函数声明;
int is_prime(int);
K&r 风格的函数定义(上古级别)
int is_prime(x)
int x; // 声明在下面
{
代码段;
}
递推是算法(数学相关)
递归(编程技巧,不是算法):
递归程序的组成部分:
0,语义信息,如fac(n)用来表示n的阶乘
1,边界条件处理
2,针对于问题的【处理过程】和【递归过程】
3,结果返回。或传出参数(地址方式)
向下递推(函数调用),向上回归(回溯)
fac(n) = n * fac(n - 1)递推式很重要
数学归纳法: 、 动态规划
①确定f(1) = 1成立
②假设f(k)成立,推f(k +1)也成立
(函数调用需要栈空间) 栈(FILO):
函数调用f(5)->f(4)->f(3)->f(2)->f(1),然后f(1)先执行,最后f(5)
BUG:爆栈(超了系统栈)、栈溢出、segmentfalut
系统栈的大小:8MB(Linux), 2MB(Windows),默认当成8MB,大概800W个字节。比如int arr[200w]就可能会爆栈了。
①我们在函数内部定义的变量、数组占用的存储空间是内存上的栈区(系统栈8MB左右)
②函数调用的层数过深,也可能造成爆栈。
如果定义全局变量的数组,则是占用全局区(静态区);
如果使用malloc家族去动态申请,则是在堆区
(在函数中使用malloc也是使用堆区,堆区需要手动释放)
----------------------------------------------
函数指针(函数的参数列表中去传入函数):
存函数地址的指针。本质是指针。是个变量,存地址的,有类型的。
// 分段函数
int g(int (*f1)(int), int (*f2)(int), int (*f3)(int), int x) {
if (x < 0) return f1(x);
if (x < 100) return f2(x);
return f3(x);
} // 传入了3个函数和1个int
int Func(int x); /*声明一个函数*/ int (*p) (int x); /*定义一个函数指针*/ p = Func; /*将Func函数的首地址赋给指针变量p*/
int (*f1)(int)
类型 (*函数名)(参数列表)
函数指针接收的 值 是 函数名(不带参数列表!)
-----------------------------------------------
OULA 45题:
从六边形遍历,跨度大,时间开销小
无需判断三角形,因为六边形数肯定是三角形数
#include <iostream>
#define MIN 143
long long Triangle(long long n) {
return n * (n + 1) >> 1;
}
long long Pentagonal(long long n) {
return n * (3 * n - 1) >> 1;
}
long long Hexagonal(long long n) {
return n * (2 * n - 1);
}
//函数指针版本二分查找 找到返回1 没找到返回0
long long binary_search(long long (*arr)(long long), int max, long long x) {
int min = MIN, mid;
while (min <= max) {
mid = (min + max) >> 1;
if (arr(mid) == x) return 1;
if (arr(mid) < x) min = mid + 1;
else max = mid - 1;
}
return 0;
}
int main() {
int n = MIN; //六边形的项数
while (n++) {
long long num = Hexagonal(n);
if (!binary_search(Pentagonal, n * 2, num)) continue;
printf("%lld\n", num);
break;
}
return 0;
}
三角形数项数< 六边形数项数 * 2
五边形数项数∈(三角形数项数, 六边形数项数)
------------------------------------------------
arr[mid] 数组
arr(mid) 函数
函数与数组的(映射)关系:
函数是压缩的数组,
数组是展开的函数。
回文整数:
CPU分支预测:
// likely表示x经常成立
#define likely(x) __builtin_expect(!!(x), 1)
// unlikely表示x不经常成立
#define unlikely(x) __builtin_expect(!!(x), 0)
串行:5 x 5
并行:5 + 4,取指令的只取指令,各司其职。
CPU最讨厌分支结构,CPU并行的,需要预先加载,如果预先加载的没执行,则大大降低CPU运行效率。
if没执行时,CPU就要已经预先加载后面的指令。
---------------------------------------------
循环结构:
while (表达式) { // 当....型循环,至少执行0次
代码块;
}
do { // 至少执行1次,先做,再判断。
代码块
} while ( 表达式); // 注意分号
for (初始化; 循环条件; 执行后操作) { // 当....型循环,至少执行0次
代码块:
}
for ( ; ; ) 里面可以省略一个或多个,在其他位置写
continue; 中止本次循环,继续下次循环。
break; 结束循环。
C语言没有左值,右值概念。
假 && 这里就不执行了,短路原则
真 || 后面就不执行了。
// 10.struct_program.c
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
int a = 0, b = 0;
if ((a++) && (b++)) { // 先a值判断后 再++
printf("true\n");
} else {
printf("false\n");
}
printf("a = %d, b = %d\n", a, b); // a = 1, b = 0 逻辑与短路原则
srand(time(0)); // 生成随机种子
int n, count = 0;
scanf("%d", &n);
for (int i = 0; i < n; i++) {
int val = rand() % 100;
count += (val & 1);
i && printf(" ");
printf("%d", val);
}
printf("\n");
printf("count: %d\n", count);
return 0;
}
输出格式:
for (int i = 0; i < 5; i++) {
i && printf(" ");
i == 0 || printf(" ");
printf("%d", i);
}
printf("\n");
stdlib.h
随机整数 rand()--> 伪随机,计算机没有真正的随机过程。
srand(time(0)); // 生成随机种子(当前系统的时间戳,单位:秒)
time.h
time(0)获取动态时间
scanf标准输入函数,从终端中输入。
printf标准输出函数,从终端中输出。
文件描述符stdin标准输入,
文件描述符stdout标准输出。
文件描述符EOF(看不见)文件末尾。
----------------------------------------------
sprintf(字符数组首地址, format, ...):将标准输出打印到字符串(字符数组)中
return成功打印字符的数量
sprintf常用于字符串拼接:
sscanf(str, "%d%d" , ...); 字符串的分割
char str[100] = {0};
int arr[4] = {0};
sprintf(str, "%d.%d.%d.%d", 192, 168, 1, 1);
printf("%s\n", str); // str的内容为192.168.1.1
// 字符串分割
sscanf(str, "%d.%d.%d.%d", &arr[0], &arr[1], &arr[2], &arr[3])
//FILE类型
FILE *fp = fopen("相对路径or绝对路径", "w+r+");
w+ : 如果当前文件不存在,w+可建立新文件
w: 覆盖写
a+ : 追加写
r+
b等等。
fprintf(文件(指针类型), format, ...):将标准输出打印到 文件 中
return成功打印字符的数量
fprintf(fp, "%s\n", str);
sprintf(文件(指针类型), format, ...):从文件中进行标准输入。
%c法:
// fscanf()读入文件全部并打印
FILE *fp = fopen("./output", "r+");
char temp[100] = {};
int k = 0;
while (~fscanf(fp, "%c", &temp[k++]));
printf("%s\n", temp);
fclose(fp);
%s法(推荐):
// fscanf()读入文件全部并打印
FILE *fp = fopen("./output", "r+");
char temp[100][100] = {};
int k = 0;
while (~fscanf(fp, "%[^\n]s", temp[k++])) {
fgetc(fp);
}
for (int i = 0; i < k; i++) {
printf("%s\n", temp[i]);
}
fclose(fp);
----------------------------------------------
数学运算:
=
+ - * / ( )
%
& | ^ ~
<< >>
二进制运算:
^异或,相同为0,不同为1.
逆运算:要满足交换律。
比如减法是加法的逆运算,但是加法不是减法逆运算。
因为a + b = b + a,但a - b != b - a
异或运算本身与自己就是逆运算。
a ^ b = c
c ^ b = a
c ^ a = b
a ^ a = 0; // 相同为0
0 ^ b = b; // 不同为1
面试题:
若干的数字,其中只有一个数只出现1次,其他都出现了2次,问如何快速找到?
如 2 3 2 3 1,所有运算之间异或:
2^3^2^3^1 = 2^2^3^3^1
= 0 ^ 0 ^ 1
= 1
(满足交换率)
~按位取反:
如: 0 -> 0b00000000
~0 = -1 -> 0b11111111
左移 << 左移1位相当乘2
右移 >> 右移1位相当除2
空的补符号位
循环读入:
while (~scanf("%d", &n)){
}
因为~0 = -1(EOF)
二进制补码表示负数:
补码=反码 + 1
反码=~原码
负数=~原码 + 1
方便记忆:
竖式运算0-1得到-1
00000000
-00000001
----------------
111111111
二分(折半)查找算法O(logN):
作用:查找某个元素是否存在,无论有几个,只要有就行。
首先单调性:否则需要自己排序
需要3个指针,min头指针、max尾指针、min指针 = (min + max) / 2
调整:
if (arr[mid] < x) min = mid + 1;
if (arr[mid] > x) max = mid - 1;
if (arr[mid] == x) return FIND;
if (min > max) break;
int binary_search(int* arr, int x, int n) {
int min = 0, max = n - 1, mid;
while (min <= max) {
mid = (min + max) / 2;
if (arr[mid] == x)
return mid;
if (arr[mid] < x)
min = mid + 1;
else
max = mid - 1;
}
return -1;
}
其中, / 2可优化为 >> 1
改递归:
①边界条件: min > max 或 arr[mid] == x
②更新min,max,然后甩锅给下次递归处理。
// 头递归(递推),需要回溯
int binary_search(int* arr, int min, int max, int x) {
if (min > max) return -1;
int mid = (min + max) >> 1;
if (arr[mid] == x) return mid;
if (arr[mid] < x) min = mid + 1;
else max = mid - 1;
return binary_search(arr, min, max, x);
}
尾递归是从尾部直接算到头部。
任何的编程问题都能用递归实现。
PS:快速排序不稳定O(NlogN),容易退化成O(N^2).
===========================
// 二分查找
#include <iostream>
#include <ctime>
using namespace std;
int binary_search(int* arr, int min, int max, int x) {
if (min > max) return -1;
int mid = (min + max) >> 1;
if (arr[mid] == x) return mid;
else if (arr[mid] < x) min = mid + 1;
else max = mid - 1;
return binary_search(arr, min, max, x);
}
// int min = 0, max = n - 1, mid;
// while (min <= max) {
// mid = (min + max) / 2;
// if (arr[mid] < x) min = mid + 1;
// else if (arr[mid] > x) max = mid - 1;
// else return mid;
// return -1;
// }
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8 , 9, 10};
srand(time(0));
int x = rand() % 12;
if (binary_search(arr, 0, 9, x) != -1)
cout << "Num:" << binary_search(arr, 0, 9, x) << "is x value" << endl;
else
cout << "NO" <<endl;
return 0;
}
-----------------------------------------------
OJ刷题:165(船长没做出来)
枚举素勾股数
3、 4 、5 素勾股数,三个数之间相互 互素,通过3个素勾股数可以推出其他的勾股数,
m>n ,m与n互素
a^2 + b^2 = c^2
a = m^2 - n ^2
b = 2mn
c = m^2 + n^2
指针(变量):变量有的性质,指针都有。
变量作用:存值,有大小、地址。有类型
指针变量,也是变量
指针变量,也是变量
指针变量,也是变量
指针变量占多数字节?
看OS的位数32位、64位。
32位占4B,64位占8B
地址是按照字节标号的,一个字节给一个地址,从0开始,比如32位的内存范围是:0 ~ 2^32 - 1,是4GB的大小
指针变量本身自己也有地址。
取变量地址时,取相关变量的首地址(编号最小的地址)
int a;
int *p = &a;
int **p1 = p; // 指向指针的指针
cout << p; //输出的是地址
cout << *p; // 输出的是p地址里面的值
指针变量前面的数据类型,表示指向该数据类型变量的地址。
int *p;
char *q;
//p和q的所占字节数相同。
指针变量可以参与运算 + -
如:
int *p = NULL;
// p + 1就是地址向后加了4B,向不同类型后面+1个类型的字节大小。
---------------------------------------------
// 等价转换
*p <=> a(原始变量)
p + 1 <=> &p[1]
p->filed <=> (*p).filed <=> a.filed
-> 间接引用
. 直接引用
----------------------------------------
题目:
struct Data {
int x, y;
};
struct Data a[2], *p = a;
尽可能的多的形式表示a[1].x
#include <iostream>
using namespace std;
struct Data {
int x, y;
};
Data a[2], *p = a;
int main() {
a[0].x = 111, a[0].y = 222;
a[1].x = 333, a[1].y = 444;
cout << a[1].x << endl; // 直接访问数组元素
cout << p[1].x << endl; // 使用指针数组下标访问
cout << (&a[1])->x << endl; // 数组下标和指针偏移的结合
cout << (&p[1])->x << endl; // 通过指针偏移访问
cout << (p + 1)->x << endl; // 使用指针算术结合结构体指针访问
cout << (*(p + 1)).x << endl; // 使用指针算术结合解引用操作访问
cout << (a + 1)->x << endl; // 使用数组指针算术访问
cout << (*(a + 1)).x << endl; // 使用数组指针解引用访问
cout << *(&a[0].x + 2) << endl; // 通过偏移结构体成员的地址
cout << *(&p[0].x + 2) << endl; // 通过偏移结构体成员的地址
cout << (&a[0] + 1)->x << endl; // 使用地址运算符和指针算术访问
cout << (*(&a[0] + 1)).x << endl; // 使用地址运算符和解引用访问
cout << (&p[0] + 1)->x << endl; // 使用地址运算符和指针算术访问
cout << (*(&p[0] + 1)).x << endl; // 使用地址运算符和解引用访问
cout << *((int *)a + 2) << endl; // 强制转换类型并进行指针算术访问
cout << ((int *)a + 2)[0] << endl; // 强制转换类型并当成数组访问
cout << ((int *)a)[2] << endl;
cout << *((int *)p + 2) << endl; // 使用强制类型转换和指针算术访问
cout << ((int *)p + 2)[0] << endl;
cout << ((int *)p)[2] << endl;
// 新的方法,使用char*进行偏移然后转换为Data*
cout << ((Data*)((char*)a + sizeof(Data)))->x << endl; // 套娃写法
cout << ((Data*)((char*)p + sizeof(Data)))->x << endl;
return 0;
}
函数指针:也是指针,变量,用来存函数的地址。
int (*add)(int, int); // 函数指针变量(add),int类型的,必须加( ),否则引起歧义。
int* add(int, int); // 指针函数的声明,返回值为int*
typedef int (*add)(int, int);
typedef用法:
内建类型的重命名:(类似#define)
typedef long long lint;
typedef char* pchar;
typedef unsiged char uchar;
结构体类型的重名:
typedef struct __node {
int x, y;
} Node, *PNode;
命名了两个类型:
Node:是这个结构体的类型
*PNode:PNode是这个结构体类型的指针类型
**注意是类型,不是变量!**
函数指针命名:
typedef int (*func)(int);
变量:int (*func)(int);
类型:typedef int (*func)(int);
-------------------------------------------
int main();
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char **env);
argc:接收的是外部传入命令行参数的个数。
*argv[]:存char*的一维数组,char*用来保存字符串的首地址。
**env:当前的环境变量。可当成二维的字符数组env[][],env所接收到的内容是:具体的每一个能够获取的环境变量字符串。
---------------------------------------------
求地址偏移量。
以8B作为空间对齐标准
//21.pointer.cpp
#include <stdio.h>
#define offset(T, x) { \
T tmp; \
(char *)&tmp.x - (char *)&tmp; \
}
// 花括号外侧加圆括号是为了获取最后一条语句的返回值
// #define offset(T, x) ({T tmp; (char *)&tmp.x - (char *)&tmp;})
struct Data {
int a;
char b;
double c;
};
int main() {
printf("%ld\n", offset(struct Data, a));
printf("%ld\n", offset(struct Data, b));
printf("%ld\n", offset(struct Data, c));
return 0;
}
函数不能将数据类型作为参数传入,所以要用宏
升级写法:
#define offset(T, x) (long long)&(((T *)(NULL))->x)
①先(NULL)一个空地址
②将(NULL)强制类型转换为(T *)结构体类型的指针
③成员访问(T *)(NULL))->x
④取其地址&((T *)(NULL))->x)
⑤将其地址强制类型转换为(long long型)查看其偏移量。
-----------------------------------------------
定义变量类型typedef和#define的区别:
#include <stdio.h>
#define ppchar char *
typedef char * pchar;
int main() {
pchar p1, p2;
ppchar p3, p4; //实际是 char * p3, p4;
printf("p1 = %lu, p2 = %lu\n", sizeof(p1), sizeof(p2));
printf("p3 = %lu, p4 = %lu\n", sizeof(p3), sizeof(p4));
/*
p1 = 8, p2 = 8
p3 = 8, p4 = 1
*/
return 0;
}
宏的随堂练习3:写一个log宏
#include <stdio.h>
//frm用于接收printf的第一个参数const char* format(格式控制字符串)
//printf第二个参数是变参... ,变参宏则在...前取一个名字作为标志,如args...
// 技巧:##连接,则允许某个参数为空, -E时发现frm后面无',' 是因为编译器做的优化
#ifdef DEBUG
#define log(frm, args...) { \
printf("[%s : %s : %d]", __FILE__, __func__, __LINE__);\
printf(frm, ##args); \
}
#else
#define log(frm, args...)
#endif
// 条件式编译,如果定义了DEBUG,则输出log,否则什么都不做。
// 编译时需要加-D参数, 如:gcc -DDEBUG main.c ,则会输出log内容
int main() {
int x = 123;
int y = 234;
log("%d %d\n", x, y);
log("Hello World\n"); // args前不加## 会报错
return 0;
}
技巧:##连接,则允许某个参数为空
#define contact(a, b) a##b
//int abc, def;//某些环境不能去掉,否则会报错
int abcdef = 0;
contact(abc, def) = 123123;
printf("%d", abcdef); // 返回值是123123
如果写成
#define contact(a, b) ab
则表示将contact(a, b)替换成字符串ab
gcc -DDEBUG 1.c
-D参数是指的是在预处理阶段手动加入某某宏
-------------------------------------------
宏的代码演示:
attribute宏(gcc标准宏):使其他函数优于main函数执行
#include <stdio.h>
// attribute宏:使其他函数优于main函数执行
__attribute__((constructor))
void test() {
printf("test\n");
}
// main不能省略
int main() {
return 0;
}
泛型宏:
#include <stdio.h>
// 泛型宏_Generic 根据传入内容替换字符串
#define TYPE_STR(a) _Generic((a),\
int : "%d",\
double : "%.2lf",\
char * : "%s"\
)
// 想办法加入\n
//#define TYPE(a) TYPE_SRT(a)
int main() {
int a = 123;
double r = 3.1415926;
char str[] = "hello world";
printf(TYPE_STR(a), a);
printf("\n");
printf(TYPE_STR(r), r);
printf("\n");
printf(TYPE_STR(str), str);
printf("\n");
return 0;
}
船长bug了 想办法
----------------------------------------------
字符串(2时02分):
字符数组:
0~n-1
// 定义字符数组
char str[size]
// 初始化
char str[] = "Hello"; // 将一行字符串当一个值赋值给字符数组
char str[size] = {'h', 'e', 'l', 'l', 'o'};
char str[size] = {0};
// \0 是字符杠0,(空字符),对应ASCII码是:0 0x00
// 遍历字符
for (int i = 0; str[i]; i++) {
cout << str[i];
}
头文件:string.h
函数:
strlen(str):计算字符串长度,以\0作为结束符(不统计\0,使用sizeof可以统计\0)
-----------
strcmp(str1, str2):字符串比较(<0[str1 < str2] =0[str1 = str2] >0[str1 > str2])
strcpy(dest, src):字符串拷贝(src拷贝到dest)
上面一组一直找\0,而\0可能会被误操作干掉,导致不安全。
-----------
strncmp(str1, str2, n):安全的字符串比较
strncpy(str1, str2, n):安全的字符串拷贝
安全的(工程用):先受n控制,不足n位则受\0控制
-----------
memcpy(str1, str2, n):内存拷贝,将str2指向内存的内容拷贝到str1,n表示拷贝的字节数。
memcmp(str1, str2, n):内存比较,将两段内存比较,看看里面的值是否相同。
memset(str1, c, n):内存设置,将str1所指向的空间里面,每一个字节(n个)初始化成c值
int arr[100] = {0};
等价于:
memset(arr, 0, sizeof(int) * 100);
注意:memset是按照字节赋值,除了0、-1可以正常逻辑赋值,其他的比如赋1,则实际上为:00000001 00000001 00000001 00000001
内存的操作速度比字符串快。mem可以接收非字符串的数组
-----------
sscanf(str1, format, ... );从字符串str1中读入内容。
sprintf(str1, format, ... );将内容输出到str1中
随堂练习4:
使用字符串相关方法,计算一个整型16进制表示的数位
利用sprintf
或者%x
#include <stdio.h>
#include <string.h>
int main() {
char str[11];
int n;
while (~scanf("%d", &n)) {
sprintf(str, "%X", n);
printf("%d(%s) has %lu digits\n", n, str, strlen(str));
}
return 0;
}
cppreference权威参考。
查:头文件、函数原型。
----------------------------
函数原型:
int printf(const char* format, ... );
format:格式控制字符串:
用char *format接收字符数组
printf("%d %s %c",) 的双引号表示是字符串。
... : 可变参列表(传任意多个参数)
return : 输出字符的数量
---------------------------
int scanf(const char* format, ... )
return : 成功读入的参数个数
scanf返回值为0 :是合法情况。表示成功读入0个参数 如:scanf("Hello"); 读入的参数没有给任何变量。
scanf返回值为-1:不合法,读入失败
循环读入:
while(scanf("") != EOF) { } // EOF值为-1
Ctrl +Z停止 让scanf返回值为-1
Ctrl + C停止:结束当前进程。os层面强制停止
EOF: end of file 文件末尾,隐藏的文件描述符。用来识别是文件的末尾。
Linux:一切皆文件
-------------------
sprintf(字符数组首地址, format, ...):将标准输出打印到字符串(字符数组)中
return成功打印字符的数量
fprintf(字符数组首地址, format, ...):将标准输出打印到 文件 中
return成功打印字符的数量
------------
空格、换行是分隔符。
printf()的%c能接收读入空格和换行,ASCII里的字符都能接收。
char ch[10];
scanf("%d%s", &a, ch);
printf("a = %d, ch = %c\n", a, ch[0]); // 此方式为工程开发中读入字符的用法
%s(除了空格,换行,tab)
scanf("%[^\n]s", str); // 异或\n,正则表达式
[ ]是字符匹配集:只能匹配集合中的字符,否则会退出。需配合getchar( ); 否则会造成读入死循环。
比如%[a-z]s 只能读入a-z的字符
// 1.test.c
#include <stdio.h>
int main() {
int n;
while (scanf("%d", &n) != EOF) { // 循环读入
printf(" has %d digits!\n", printf("%d", n));
}
return 0;
}
// 2.test.c
#include <stdio.h>
int main() {
char str[100] = {0};
while (scanf("%[^\n]s", str) != EOF) {
getchar(); // 吞掉循环读入最后输入的\n,否则会造成无限死循环
printf(" has %d chars!\n", printf("%s", str)); // 输出字符串的字符数
}
return 0;
}
math.h
double pow(double a, double b); // 指数函数
a:底数 b:指数
返回值:a的b次方
double sqrt(double x); // 开平方函数
x:被开方数
返回值:根号x的结果
double ceil(double x); // 上取整函数
x:某个实数
返回值:x上取整的结果(如:ceil(4.1) = 5)
double floor(double x); // 下取整函数
x:某个实数
返回值:x下取整的结果(如:ceil(4.9) = 4)
double fabs(double x); // 实数绝对值函数
x:某个实数
返回值:返回|x|的结果
double log(double x); // 以e为底对数函数
x:某个实数
返回值:返回log e (x)的结果
double log10(double x); // 以10为底对数函数
x:某个实数
返回值:返回log 10 (x)的结果
(其他为底需要换底公式:
比如log 2 (8) = log 10 (8) / log 10 (2)
)
double acos(double x); // arccos(x)函数
x:角度的弧度值
返回值:返回arccose(x)的结果
如:acos(-1) = Π
(转换)弧度制=角度值x * Π / 180
stdlib.h
int abs(int x); // 绝对值函数
x:某个整数
返回值:返回|x|的结果
----------------------------------------------
随堂题目1:求x的立方根
double x;
scanf("%lf", &x);
printf("%lf", pow(x, 1.0 / 3.0));
随堂题目2:读入角度值,转化为弧度值
#define PI = acos(-1)
double x;
scanf("%lf", &x);
printf("%lf", PI / 180.0 * x);
极客学院:学习C语言头文件。
https://www.jikexueyuan.com/wiki/c?from=WikiSubject
__typeof() C语言自带的宏,替换类型。
奇偶数判断: n % 2比 n & 1慢30倍
-----------------------------
!!(x) 对x进行逻辑归一化,所有的真值都变成整型1,所有的假值都变成整型0.
more code more bug
<=判断2次
<判断1次
{ } 里面组合成一个复合语句。
int a = 1, b = 2; 以;结尾是单语句
switch(n) {
case 1:
cout << "ONE" << endl;
break;
default:
cout << "ERROR" << endl;
}
表达式有返回值
if else的逻辑怎么替换?
条件运算符:
(... ? ... : ...)三目运算符
HZOJ120题日期合法性