结构体运算符与取余_C语言基础

本文详细介绍了C语言的基础知识,包括结构体运算符、内存管理、位运算、数据类型内存大小、标准库函数如memset和qsort的使用,以及C与C++的区别。特别讨论了结构体内存对齐规则、指针和数组的使用,以及如何在函数中正确初始化和传递结构体。文章还通过例子讲解了如何使用uthash库实现哈希表,包括添加、查找、删除、排序和遍历哈希表项。
摘要由CSDN通过智能技术生成

C语言知识点整理

C基础知识点

malloc和free

malloc 开辟二维数组的示例。leetcode 122。

int maxProfit(int* prices, int pricesSize){

int **dp = (int**)malloc(pricesSize*sizeof(int*));

for (int i = 0; i < pricesSize; i++) {

dp[i] = (int*)malloc(2*sizeof(int));

}

dp[0][0] = 0; dp[0][1] = -prices[0];

for (int i = 1; i < pricesSize; i++) {

dp[i][0] = fmax((dp[i-1][1] + prices[i]), dp[i-1][0]);

dp[i][1] = fmax((dp[i-1][0] - prices[i]), dp[i-1][1]);

}

int ans = fmax(dp[pricesSize - 1][0], dp[pricesSize - 1][1]);

for (int i = 0; i < pricesSize; i++) {

free(dp[i]);

}

free(dp);

return ans;

}

scanf 和 printf的输出格式控制

控制符

说明

%d

按十进制整型数据的实际长度输出。

%ld

输出长整型数据。

%md

m 为指定的输出字段的宽度。如果数据的位数小于 m,则左端补以空格,若大于 m,则按实际位数输出。

%u

输出无符号整型(unsigned)。输出无符号整型时也可以用 %d,这时是将无符号转换成有符号数,然后输出。但编程的时候最好不要这么写,因为这样要进行一次转换,使 CPU 多做一次无用功。

%c

用来输出一个字符。

%f

用来输出实数,包括单精度和双精度,以小数形式输出。不指定字段宽度,由系统自动指定,整数部分全部输出,小数部分输出 6 位,超过 6 位的四舍五入。

%.mf

输出实数时小数点后保留 m 位,注意 m 前面有个点。

%o

以八进制整数形式输出,相比十六进制和十进制用的很少。

%s

用来输出字符串。用 %s 输出字符串同前面直接输出字符串是一样的。但是此时要先定义字符数组或字符指针存储或指向字符串。

%x(或 %X 或 %#x 或 %#X)

以十六进制形式输出整数,如果是小写的x,输出的字母就是小写的;如果是大写的X,输出的字母就是大写的;如果加一个#,就以标准的十六进制形式输出。

C 不同变量的内存占用

static变量在静态区,对于包含static变量的实例,sizeof均不纳入计算

不同编辑器的默认对齐大小不一样,gcc是4字节对齐

编辑器规定n字节对齐的pack指令 #pragma pack(n)

在编译阶段处理,sizeof作用范围内的内容不能被编译,所以sizeof()内的运算不被执行

sizeof(函数)=sizeof(返回值类型)

sizeof和strlen:sizeof计算字符串容量,算’\0’,strlen计算字符串长度,到’\0’截止

对于C++的string类,string的实现在各库中可能有所不同,但是在同一库中相同一点是,无论你的string里放多长的字符串,它的sizeof()都是固定的,字符串所占的空间是从堆中动态分配的,与sizeof()无关。 sizeof(string)=4可能是最典型的实现之一,不过也有sizeof()为12、32字节的库实现。

32/64 位 系统各类型内存大小对照(有无unsigned修饰都一样,大小单位字节byte):

系统

32位

64位

char

1

1

short

2

2

int

4

4

float

4

4

long

4

8

*(地址)

4

8

double

8

8

long long

8

8

对于不同系统,有细致的不一样的地方,32位和64位系统在Windows下基本数据类型的大小都是一样的。只有指针的大小不一样!32位指针大小为4byte,而64位的指针大小为8byte。

Linux下,long型是64位的,这一点是和Windows不同的地方。(表中是Linux标准的long)

C语言位运算

首先位运算最多的肯定是左移和右移操作。

左移

对于左移,丢弃最高位,低位补0, 左移 n 位, 相当于乘以

。譬如 二进制的 000...001,左移两位 就是 000... 100。

对于有符号数,左移可能会导致符号位变化导致的溢出。

int是有符号的整形数,左端的1位是符号为,即0为正1为负,那么用移位的时候就会出现溢出,如:

int i=0x40000000;//16进制的40000000,为2进制的01000000...0000

i=i<<1;

//那么,i在移动1位之后就会变成0x80000000,也就是2进制的100000...0000,符号位被置1,起位全是0,变成了int类型所能表达的最小值,32为的int这个值是-2147483648,溢出,如果在接着把i左移1位会出现什么情况呢?在c语言都采用了丢弃最高位的处理方法,丢弃了1之后,i的值变成了0

其他的左移特殊情况是,当左移的位数超过该数值类型的最大位数时候,编译器会用左移的位数模类型的最大位数,例如 对于32 位 类型,左移33位,这时候其实就相当于只左移了一位。

int i=1,j=0x80000000;//设int为32位

i=i<<33;//33%32=1 左移动1位,i变成2

j=j<<33;//33%32=1 左移动1位,j变成0,最高为被丢弃

右移

对于右移,对符号位的处理和左移不同,对于有符号整数来说,比如int类型,右移会保持符号位不变。

具体操作,符号位向右移动后,本来是正数的会在最前面会补0,本来是负数的会在最前面补1,以此保持符号位的不变。也就是汇编语言中的算术右移.同样当移动的位数超过类型的长度时,会取余数,然后移动余数个位。

结构体内存计算

约定为32位系统,即char 1字节、short 2字节、int 4字节

该问题总结为两条规律:

每个结构体成员的起始地址为该成员大小的整数倍,即int型成员的起始地址只能为0、4、8等

结构体的大小为其中最大成员大小的整数倍

struct A{

char a; //1

int b; //空3 + 4 = 7 (规则1)

short c; //2+空2=4 (规则2)

};

struct B{

char a; //1

short b; //空1 + 2 = 3 (规则1)

int c; //4

};

如果指定了对齐值

#pragma pack(1)

struct A{

char a; //1

int b;//4

short c;//2

};

#pragma pack(1)

struct B{

char a;//1

short b;//4

int c;//2

};

这时候结构体A和B的大小都为7

点运算符和箭头运算符

这两个都是用于成员变量的获取,只看C的话基本就是对结构体变量或者结构体指针的操作。之前也比较明晰的是点运算符用于结构体对象,箭头运算符对应的是结构体指针。

实际运用中有点小细节。对于我们开辟的一个结构体数组,例如

struct Article arrArticle[10];

arrArticle,是一个指向第一个数组元素的常量指针。所以 arrArticle->number 指向第一个数组元素的成员 number。简单地说,对于任一的索引值 i,下面 3 个表达式是等价的:(注意按照下标访问和数组基指针进行加减访问在成员访问运算符选择上的区别)

arrArticle[i].number

(arrArticle+i)->number

(*(arrArticle+i)).number

C的一些常用标准库函数

memset()

该库函数属于C库函数 。复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。

void *memset(void *str, int c, size_t n)

虽然本意是用于字符串,但是也支持对int初始数组进行赋初值, 一般赋值全为0, 或者用 0x3f填满,达到构造类似于INT_MAX的功能。例如使用 0x3f进行赋值,stack里面的每个int数值被 初始化为0x3f3f3f3f,即为1061109567。

int stack[10];

memset(stack, 0, sizeof(stack)); // memset(stack, 0x3f, sizeof(stack))

for (int i = 0; i < 10; i++) {

printf("%d ", stack[i]);

}

如果是直接进行初始值的定义使用花括号。

int arr[2][3] = {{1,2,3}, {4,5,6}}

qsort()

void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))

base -- 指向要排序的数组的第一个元素的指针。

nitems -- 由 base 指向的数组中元素的个数。

size -- 数组中每个元素的大小,以字节为单位。

compar -- 用来比较两个元素的函数。

#include

#include

int values[] = { 88, 56, 100, 2, 25 };

int cmpfunc (const void * a, const void * b)

{

return ( *(int*)a - *(int*)b );

}

int main()

{

int n;

printf("排序之前的列表:\n");

for( n = 0 ; n < 5; n++ ) {

printf("%d ", values[n]);

}

qsort(values, 5, sizeof(int), cmpfunc);

printf("\n排序之后的列表:\n");

for( n = 0 ; n < 5; n++ ) {

printf("%d ", values[n]);

}

return(0);

}

解释下比较函数的写法:

在一些函数定义中,const void *a 这类形参,const void *a这是定义了一个指针,a可以指向任意类型的值,但它指向的值必须是常量,在这种情况下,我们不能修改被指向的对象,但可以使指针指向其他对象。void *则为“无类型指针”,void *可以指向任何类型的数据。

后面 *(int*) a 分为两步 (int*) a是把上面的无类型指针转换为 int 型指针, 再前面加上 * 变成 *(int*) a 是进行了一个dereference解引用。

不过其实传参也可以直接传个 int 类型的指针。

int cmpfunc(int* a, int* b) {

return *a - *b;

}

除此之外,qsort 也比较适用于对结构体进行排序。

struct In

{

int x;

int y;

}s[100];

//按照x从小到大排序,当x相等时按照y从大到小排序

int cmp( const void *a , const void *b )

{

struct In *c = (In *)a;

struct In *d = (In *)b;

if(c->x != d->x) return c->x - d->x;

else return d->y - c->y;

}

qsort(s,100,sizeof(s[0]),cmp);

C和C++的一些区别

没有引用的概念,需要二级指针

C里没有引用,C++里才有,也就没有引用传值之类的。所以会涉及一些二级指针的概念。对函数来说,它所传递的任何参数仅仅是原来参数的一个拷贝。C语言里,改变值只能通过指针(地址)方式进行传递,传递数组也可以改变值,但实际上,传递数组就是传递指针,也就是默认传入了数组的头元素指针。

摘引博客给出一个样例

首先给出一个错误代码

#include

#include

typedef struct LNode

{

int data;

struct LNode *next;

}LNode;

void InitLinkList(LNode *L)

{

L=(LNode *)malloc(sizeof(LNode));

L->data=0;

L->next=NULL;

}

int main()

{

LNode *L=NULL;

InitLinkList(L);

printf("%p\n",L);

return 0;

}

错误点一:该InitLinkList并不能真正初始化一个链表头结点,在函数里我们的确是给L分配了内存,初始化了结点,但是主函数里没有把指针L本身的地址传递进去,InitLinkList()里的L并不是main()里的L,虽然名称是一样的,但是InitLinks()的L是局部的(所以,其实你写成a,b,c,d都没关系),传进来的只是一个LNode*副本,这个副本和外面的L的内容是一样的,但是变量不是同一个,当这个子函数执行完后,main()里的L还是原来的L。

错误点二:没有释放malloc的内存,在InitLinkList函数中通过malloc分配的内存是通过堆来划分的,这意味着函数调用完毕后,内存不能自动释放,将会造成内存泄漏。

第一个修改方案是直接利用return返回值进行对main函数中L的赋值。

#include

#include

typedef struct LNode

{

int data;

struct LNode *next;

}LNode;

LNode * InitLinkList(LNode *L)

{

L=(LNode *)malloc(sizeof(LNode));

L->data=0;

L->next=NULL;

return L;

}

int main()

{

LNode *L=NULL;

L=InitLinkList(L);

printf("%p\n",L);

return 0;

}

要么就是修正主函数里面没有直接传入L的地址的错误,在功能函数参数中传入一个二级指针。

#include

#include

typedef struct LNode

{

int data;

struct LNode *next;

}LNode;

void InitLinkList(LNode **L)

{

(*L)=(LNode *)malloc(sizeof(LNode));

(*L)->data=0;

(*L)->next=NULL;

}

int main()

{

LNode *L=NULL;

InitLinkList(&L);

printf("%p\n",L);

return 0;

}

理解二级指针的关键在于,在main中L本身就是一个指针,指向某块内存。取L这个变量的地址作为实参内容传递给initLinkList()函数里的形参,形参就得用指针的指针来存储这个地址变量。那么子函数里面的L的内容不就是main()里L地址么,那么,*L不就是main里L的内容么,也就是说,对*L操作就是对main()里的L进行操作。

如果是数组传参,会简单些,我们可以看看传参上的区别。

#include

#include

#include

typedef struct{

int x;

}test_struct;

void init(test_struct* input){

input->x = 1;

input[1].x = 2;

input[2].x = 3;

}

int main() {

test_struct *slist = (test_struct*)malloc(sizeof(test_struct)*3);

init(slist);

for (int i = 0; i < 3; i++) {

printf("val: %d \n", slist[i].x);

}

return 0;

}

复习下之前的内容:对于我们开辟的一个结构体数组,例如

struct Article arrArticle[10];

arrArticle,是一个指向第一个数组元素的常量指针。所以 arrArticle->number 指向第一个数组元素的成员 number。简单地说,对于任一的索引值 i,下面 3 个表达式是等价的:(注意按照下标访问和数组基指针进行加减访问在成员访问运算符选择上的区别)

arrArticle[i].number

(arrArticle+i)->number

(*(arrArticle+i)).number

struct 和 typedef struct

在C++ 中, 虽然结构体用的也不多,结构体的定义模式是这样的:

struct Student{

int name;

};

于是就定义了结构体类型Student,声明变量时直接Student stu1;

在C里面,定义结构体的写法

typedef struct Student

{

int name;

}Stu;

于是在声明变量的时候就可:Stu stu1;(如果没有typedef就必须用struct Student stu1;来声明)这里的Stu实际上就是struct Student的别名。

在C++里面也可以附加typedef进行别名的设置。

struct Student

{

int name;

}stu1; // 这边的stu1是一个变量

typedef struct Student2

{

int name;

}stu2; // 这边就是一个别名

uthash

uthash 作为一个头文件。可以通过添加UT_hash_handle把任意一个C结构体中的一个或者多个数值组合起来作为key达到hash的功能。

初始化

1、uthash需要自定义数据结构, 一个包含UT_hash_handle hh的结构体。

2、结构体中需要定义key的数据类型和其余作为val的数据类型。

struct my_struct {

int id;

char name[10];

UT_hash_handle hh;

};

在定义后需要初始化一个空指针指向定义的hash结构。这里的hash表结构类似于一个双向链表,这边设置的这个指针就类似于链表的头结点。

struct my_struct *users = NULL;

之后为hash表结构开辟合适的空间,再进行添加item的操作。

添加

对应不同的key的数据类型,需要使用不同的添加函数。

key是int,可以使用 HASH_ADD_INT

key是字符串,可以使用 HASH_ADD_STR

key是指针,可以使用 HASH_ADD_PTR

其它,可以使用 HASH_ADD,上述实际都是调用这个方法,不过简化了参数

在上面已经使用过HASH_ADD_INT , 第一个参数是之前定义的结构体空指针,第二个参数申明了会使用结构体中的哪部分作为可以hash的键值,第三个参数就是我们上面完成赋值初始化构建出的结构体实例s。

void add_user(int user_id, char *name) {

struct my_struct *s;

s = malloc(sizeof(struct my_struct));

s->id = user_id;

strcpy(s->name, name);

HASH_ADD_INT(users, id, s); /* id: name of key field */

}

查找

添加函数一样,不同的key数据类型,有不同的添加函数。如HASH_FIND_INT查询int类型的键值。

struct my_struct *find_user(int user_id) {

struct my_struct *s;

HASH_FIND_INT( users, &user_id, s);

return s;

}

这里第一个参数是对应需要查询的哈希表,第二个参数指向查询的key的地址,最后一个s 用于存储查询结果(结构体指针s提前创建了,如果没有查询到 s 返回是 NULL,否则就返回对应的key和val)。

对于键值,我们在添加的时候需要保证键值不重复添加。所以对上面的版本进行修改。如果 没有找到对应的键值,创建完整的key和val。如果找到了,add变成覆盖原来的val。

void add_user(int user_id, char *name) {

struct my_struct *s;

HASH_FIND_INT(users, &user_id, s);

if (s == NULL) {

/* 如果 s 为 NULL,需要重新 malloc

struct my_struct *s;

s = malloc(sizeof(struct my_struct));

*/

s = (struct my_struct *)malloc(sizeof *s);

s->id = user_id;

HASH_ADD_INT(users, id, s);

}

strcpy(s->name, name);

}

在上面的例子里面 users 作为一个全局变量,如果要设计成函数传参传递数值呢。

直接把 这个 hash表的 指针加在 add_user 函数里面是不对的。

错误写法

void add_user(struct my_struct *users, int user_id, char *names) {

...

HASH_FIND_INT(users, id, s);

}

需要传入的是指向这个 hash表指针的指针, 可以参考上面的双重指针的说明。因为我们在HASH_ADD函数里,需要的是hash表指针本身的地址,而不是这个指针指向的具体内容。

正确写法

void add_user(struct my_struct **users, int user_id, char *name) {

...

HASH_ADD_INT(*users, id, s);

}

删除

删除一个的元素的示例代码:

void delete_user(struct my_struct *user) {

HASH_DEL(users, user); // user: pointer to delete

free(user); // optional,up to you

}

HASH_DEL只会把一个结构体实例从哈希表里面删除,但是不会 free 掉

如果希望循环删除掉所有的item并且free掉每个对象的空间。

void delete_all() {

struct my_struct *current_user, *tmp;

HASH_ITER(hh, users, current_user, tmp) {

HASH_DEL(users,current_user); /* delete; users advances to next */

free(current_user); /* optional- if you want to free */

}

}

如果只是想把哈希表结构删除,但是不用free掉每个元素。

HASH_CLEAR(hh,users);

之后,users 这个指针被设置为NULL。

计数

HASH_COUNT 用于计算hash表中item的总数目

unsigned int num_users;

num_users = HASH_COUNT(users);

printf("there are %u users\n", num_users);

遍历

A hash is also a doubly-linked list.

Iterating backward and forward through the items in the hash is possible because of the hh.prev and hh.next fields. All the items in the hash can be reached by repeatedly following these pointers, thus the hash is also a doubly-linked list.

可以使用 hh.next 指针,指向下一个。

void print_users() {

struct my_struct *s;

for(s=users; s != NULL; s=s->hh.next) {

printf("user id %d: name %s\n", s->id, s->name);

}

}

还有就是上面删除部分用过的HASH_ITER

排序

对于排序来讲可以根据key或者根据val,或者结合起来

HASH_SORT(users, name_sort);

第二个参数是一个比较函数,返回值是需要为int。

int sort_function(void *a, void *b) {

/* compare a to b (cast a and b appropriately)

* return (int) -1 if (a < b)

* return (int) 0 if (a == b)

* return (int) 1 if (a > b)

*/

}

使用键值或者数值进行比较的示例。

int name_sort(struct my_struct *a, struct my_struct *b) {

return strcmp(a->name,b->name);

}

int id_sort(struct my_struct *a, struct my_struct *b) {

return (a->id - b->id);

}

void sort_by_name() {

HASH_SORT(users, name_sort);

}

void sort_by_id() {

HASH_SORT(users, id_sort);

}

When the items in the hash are sorted, the first item may change position. In the example above, users may point to a different structure after calling HASH_SORT.

实例1(LeetCode387)

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

示例:

s = "leetcode"

返回 0

s = "loveleetcode"

返回 2

代码

struct hashTable {

int key;

int val;

UT_hash_handle hh;

};

void char_add(struct hashTable **hashHead, int key, int pos) {

struct hashTable *tmp;

HASH_FIND_INT(*hashHead, &key, tmp);

if (tmp != NULL) {

tmp->val = -1;

} else {

tmp = (struct hashTable*)malloc(sizeof *tmp);

tmp->key = key;

tmp->val = pos;

HASH_ADD_INT(*hashHead, key, tmp);

}

}

int find_no_duplicate(struct hashTable **hashHead, int length) {

struct hashTable *tmp;

int ans = length + 1;

for (tmp = *hashHead; tmp != NULL; tmp = tmp->hh.next) {

if (tmp->val != -1) {

ans = fmin(tmp->val, ans);

}

}

return (ans == length + 1)? -1 : ans;

}

void print(struct hashTable **hashHead) {

struct hashTable *s;

for(s=*hashHead; s != NULL; s=s->hh.next) {

printf("key %d: val %d\n", s->key, s->val);

}

}

int firstUniqChar(char * s){

struct hashTable *hashHead = NULL;

int length = strlen(s);

if (length == 0) return -1;

for(int i = 0; i < length; i++) {

char_add(&hashHead, s[i] , i);

}

//print(&hashHead);

return find_no_duplicate(&hashHead, length);

}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值