默认计划
475人加入学习
(0人评价)
【研发工程师】C语言程序设计
价格 ¥ 970.00
该课程属于 名企研发核心能力课 请加入后再学习

数字反转:

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次。速度更快。

 

标记->数组

线性筛:空间复杂度时间复杂度都为 O(N)

作用:筛选一定范围内所有的素数。

核心思想:用整数 i * prime[j] (小于 i 最小素因数的素数表)去 数组 合数 prime[j] * i。(PS: 素数筛的 i 是素数,这里的 i 是素数+合数)


其中 prime[j] * ii 有如下性质:

  1. prime[j] * i 中最小的素数为 prime[j]
  2. prime[j] * i 可以表示成为 prime[j] * i
  3. prime[j] 一定 <= i 中最小的素因子
  4. 利用 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

  • 30 的因子是 2,3,5,6,10,15。
  • 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 = 8
  • i=25,可以数组的 prime[j] * i:25×2, 25×3, 25×5
  • i=45,可以数组的 prime[j] * i:45×2, 45×3
  • 能数组 90 的 i 等于: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

 

 

作业:求解因子和问题

 

 

[展开全文]
灰酱 · 29天前 · 线性筛 0

二分查找: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;
}
  1. %g用于打印浮点型数据时,会去掉多余的零,至多保留六位有效数字(不同于%e的默认保留小数点后6位)

  2. 1e-7是10的-7次方

  3. 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) 函数

 

函数与数组的(映射)关系:

函数是压缩的数组,

数组是展开的函数。

[展开全文]
灰酱 · 2024-01-22 · 函数 0

回文整数:

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

 

 

[展开全文]
灰酱 · 2024-01-02 · 数学运算 0

二分(折半)查找算法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题日期合法性

[展开全文]

授课教师

C++算法工程师

课程特色

视频(31)

学员动态

LBCL 加入学习
Ton96 加入学习