0%

C语言唯一的三元操作符

如果有两个变量,我希望打印出最大的一个,怎么做呢,很简单一个if-else语句搞定,嗯嗯,👍。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*beginner/ternary/ternary1.c*/
#include <stdio.h>

int main()
{
int a = 5;
int b = 7;
if (a > b)
printf("Bigger is %d\n", a);
else
printf("Bigger is %d\n", b);

return 0;
}

今天来一个比较搞定的三元操作符,前面有提过什么事三元操作符,参考逻辑运算符原文,简单的理解就是:a?b:c,如果a为真就返回b,如果a为假就返回c。

所以rst = a?b:cif-else来表达就是:

1
2
3
4
if (a)
rst = b;
else
rst = c;

所以上面的程序可以简化为:

1
2
3
4
5
6
7
8
9
10
11
12
/*beginner/ternary/ternary2.c*/
#include <stdio.h>

int main()
{
int a = 5;
int b = 7;

printf("Bigger is %d\n", a > b ? a : b);

return 0;
}

看到没有,奇迹就是用1行能写出4行的效果。

或许你会说用python可以1行写出c语言10行甚至100的功能,这个我承认,所以下一阶段开讲Python

或许你问了,有了if-else还要这个干吗,简单,我们可以做很多事情,比如在宏定义里面定义下面的一行

1
#define MAX(a,b) a>b?a:b

后面直接调用MAX就可以得出最大值了,不过这个写法目前看来OK,如果严谨说起来是有bug的,且听下回分解。

另外一个比较有用的例子如下,我觉得很适合英语国家,在我们中国不存在这个问题,就是如果你有一个叔叔和几个叔叔的表达方式不一样:

  • 中文:我有一个叔叔,我有N个叔叔(N>1)
  • 英文:我有一个uncle,我有N个uncles(N>1)

看到没有,有好几个叔叔就要加个s,这个怎么来办呢,有了三元操作符,解决方案如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*beginner/ternary/ternary3.c*/
#include <stdio.h>

int main()
{
int n1 = 1;
int n3 = 3;

printf("I have %d uncle%s\n", n1, n1==1 ? "" : "s");
printf("I have %d uncle%s\n", n3, n3==1 ? "" : "s");

return 0;
}

编译

编译也有所不同,方法为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#beginner/ternary/Makefile
ALL : ternary1 ternary2 ternary3

ternary1: ternary1.c
gcc -o ternary1 ternary1.c

ternary2: ternary2.c
gcc -o ternary2 ternary2.c

ternary3: ternary3.c
gcc -o ternary3 ternary3.c

.PHONY : clean

clean:
rm -f ternary1 ternary2 ternary3

这会产生可执行程序,当程序被执行时,它会产生下列结果:

1
2
3
4
5
6
7
8
9
$ ./ternary1
Bigger is 7

$ ./ternary2
Bigger is 7

$ ./ternary3
I have 1 uncle
I have 3 uncles

C语言的存储类型关键字

存储类型定义了C程序中变量与函数的可见范围和生命周期。

这些关键字位于修饰的函数或变量之前,主要说说下面几个:

  • auto
  • register
  • static
  • extern (后面再说)

auto 存储类型

auto 存储类是所有局部变量默认的存储类型,不管写不写,默认是auto类型的,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*beginner/auto/auto1.c*/
#include <stdio.h>

int main()
{
int year = 2011;
auto int month = 2;

printf("Year : %d\n", year);
printf("Month: %d\n", month);

return 0;
}

上面的实例定义了两个带有相同存储类的变量。

auto 只能用在函数内,即 auto 只能修饰局部变量

register 存储类型

register 存储类型用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 & 运算符(因为它没有内存位置)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*beginner/register/register2.c*/
#include <stdio.h>

int main()
{
register int sum = 0;
int i = 0;

for (i = 0; i <= 100; i++)
{
sum += i;
printf("Sum : %d\n", sum);
}

return 0;
}


寄存器主要用于快速访问及变化的变量,比如计数器。不过,定义 register 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,只是可能,这取决于硬件和实现的限制,所以这个一般不太实用,除非对系统很精通,确认是寄存器使用,才能起到加速的效果。

static 存储类型

static有多重用法,这里先说一下作为存储类型的用法,这个功能对于需要在某个函数进行初始化且需要保持状态作用大大滴。

  • static 存储类型指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。

  • static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内,还未说到多个文件,后续再介绍

  • 全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。

以下实例演示了 static 修饰全局变量和局部变量的应用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*beginner/static/static3.c*/
#include <stdio.h>

void localfunc(void);

static int global = 10;

int main()
{
while (global--)
{
localfunc();
}
return 0;
}
void localfunc(void)
{
static int local = 5;
local++;
printf(" local : %d , global : %d\n", local, global);
}

可以看到运行的结果为:

1
2
3
4
5
6
7
8
9
10
11
$ ./storage3
local : 6 , global : 9
local : 7 , global : 8
local : 8 , global : 7
local : 9 , global : 6
local : 10 , global : 5
local : 11 , global : 4
local : 12 , global : 3
local : 13 , global : 2
local : 14 , global : 1
local : 15 , global : 0

C语言的存储类型关键字

前面说了autoregisterstatic存储类型,这次说一下extern存储类型。

在使用这个关键字之前,需要了解多个函数的编译,什么意思呢,就是文件a.c会使用b.c或者c.c的变量、函数等,在编译的时候也需要提供所有的文件。

下面来看看extern的概念吧。

extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。

当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。

extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候,如下所示:

第一个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
/*beginner/extern/extern1.c*/
#include <stdio.h>

int number;

extern void test();

int main()
{
number = 10;
test();
return 0;
}

第二个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/*beginner/extern/test.c*/
#include <stdio.h>

extern int number;

void test()
{
int i;
for (i = 0; i < 10; i++)
{
number -= 1;
printf("number is %d\n", number);
}
}

可以看到两个文件互为externextern1.c调用test.ctest函数,test.c调用extern1.cnumber变量。

编译也有所不同,方法为:

1
$ gcc -o extern1 extern1.c test.c

这会产生可执行程序,当程序被执行时,它会产生下列结果:

1
2
3
4
5
6
7
8
9
10
11
$ ./extern1
number is 9
number is 8
number is 7
number is 6
number is 5
number is 4
number is 3
number is 2
number is 1
number is 0

vim中的删除操作大全

删除单个字符、词或者行

删除类似于拷贝,然而你必须使用d替代y。

描述
x 删除当前字符
dw 删除当前词
dj 删除当前行和下一行

这里应该可以出来以及技巧,x是删除,p是粘贴,
所以xp不是系统而是交互两个字符

VI下删除文本中的^M

关于回车与换行

很久以前,老式的电传打字机使用两个字符来另起新行。一个字符把滑动架移回首位 (称为回车,ASCII码为0D),另一个字符把纸上移一行 (称为换行, ASCII码为0A)。当计算机问世以后,存储曾经非常昂贵。有些人就认定没必要用两个字符来表示行尾。UNIX 开发者决定他们可以用一个字符来表示行尾,linux沿袭Unix,也是如此。而Apple 开发者规定了用r,MS-DOS以及Windows 则继续使用rn表示,所以换行就有了3种方法。

三种行尾格式如下:

  • unix : n
  • dos: rn
  • mac : r

这意味着,如果你试图把一个文件从一种系统移到另一种系统,那么你就有换行符方面的麻烦。

​ 因为MS-DOS及Windows是回车+换行来表示换行,因此在Linux下用Vim查看在Windows下写的代码,行尾后“^M”符号。

在Vim中解决这个问题,很简单,在Vim中利用替换功能就可以将“^M”都删掉,键入如下替换命令行:

1
:%s/^M//g

注意:

上述命令行中的“^M”符,不是“^”再加上“M”,而是由“Ctrl+v”、“Ctrl+M”键生成的,或者Ctrl+v,再按回车。

或者使用这个命令:

1
:% s/r//g

vim删除包含指定字符串的行

在命令模式中,使用如下指令删除包含指定字符串的行:

1
:g/string/d

vim删除不包含指定字符串的行

在命令模式中,使用如下指令删除不包含指定字符串的行:

1
:v/xxx/d

删除缺失的字符 There is no ‏ (U+200F)

1
$ sed -i "s/$(echo -ne '\u200b')//g" file

或者在vim中如此操作:

1
%s/\%u200b//

vim删除重复行

使用vim内建的功能,如下即可快速在排序后删除重复的行:

1
:sort u

删除空行

:g/^$/d

删除空行以及只有空格的行

:g/^\s*$/d

删除以 # 开头或 空格# 或 tab#开头的行

:g/^\s*#/d

删除以 ; 开头或 空格; 或 tab;开头的行

:g/^\s*;/d

使用正则表达式删除行

  • 如果当前行包含 hello ,则删除当前行
    :/hello/d
  • 删除从第二行到包含 hello 的区间行
    :2,/hello/d
  • 删除从包含 hello 的行到最后一行区间的行
    :/hello/,$d
  • 删除所有包含 hello 的行
    :g/hello/d
  • 删除匹配 hello 且前面只有一个字符的行
    :g/.hello/d
  • 删除匹配 hello 且以它开头的行
    :g/^hello/d
  • 删除匹配 hello 且以它结尾的行
    :g/hello$/d
  • .ini 的注释是以 ; 开始的,如果注释不在行开头,那么删除 ; 及以后的字符
    :%s/\;.\+//g
  • 删除 # 之后所有字符
    %s/\#.*//g

Vim 删除所有行的指定字符到每行末尾的字符

:%s/ABCD.*$//g : 删除所有行的指定字符ABCD到每行末尾的字符

| 键 | 描述 |
| p(小写的p) | 立即粘贴到当前光标位置之后 |
| P(大写的P) | 立即粘贴到当前光标位置之前 |

如果你执行几次的删除操作,如果你希望粘贴这些删除的字符,使用下面的方法。

首先,查看寄存器,使用下面的命令:

1
:reg

最近的删除内容会显示在0-9寄存器中,记下你想粘贴的删除词语的寄存器位置。

如果你想粘贴寄存器标号为N的词组,执行下面命令:

1
“Np

C语言的结构体位域

前面可以看到,使用unoin共用体可以节省数据的存储空间。

同样,在结构体或者共用体中,使用位域也可以达到这个效果。

先看看什么时候可以使用位域,这个特点大多数人都不会用到,用到的大部分人都基本跟底层打交道,比如驱动开发、单片机开发等。

先看一个最简单的例子,比如我们的红绿灯系统,先定义一个结构体:

1
2
3
4
5
6
typedef struct
{
unsigned int red;
unsigned int green;
unsigned int yellow;
} TrafficLight;

此时如果看一下TrafficLight结构体的大小,应该是12个字节

但是我们知道对于这几种灯而言,只有2中状态,开和关,也就是1和0,也就是1个bit其实就能表达,所以针对这种情况,有了位域的概念,先看一下位域的声明:

1
2
3
4
typedef struct 
{
type name : width;
}
  • type:整数类型
  • name:为位域的名称
  • width:为位域中位的数量,其值需要小于等于type指定的类型大小

所以交通灯的结构体使用位域的概念就如下所示:

1
2
3
4
5
6
typedef struct
{
unsigned int red : 1;
unsigned int green : 1;
unsigned int yellow : 1;
} TrafficLight1;

三色红绿灯加起来一共需要3个bit,所以一个无符号整型就可以容纳这些值了,此时看一下这个结构体的长度,应该为4

总结一下:

当结构体或共用体中有无符号整型或有符号整型成员时,C语言允许用户指定这些成员所占用的存储位数,即位域。通过将数据存储在它们所需的最小数目的存储位内,位域能够有效地提供存储空间的利用率,但是,要注意,位域成员必须被声明为有符号整型或无符号整型

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/*beginner/struct/struct6.c*/
#include <stdio.h>

int main()
{

typedef struct
{
unsigned int red;
unsigned int green;
unsigned int yellow;
} TrafficLight;

TrafficLight trafficlight;

printf("The size of TrafficLight %d\n", sizeof(trafficlight));

typedef struct
{
unsigned int red : 1;
unsigned int green : 1;
unsigned int yellow : 1;
} TrafficLight1;

TrafficLight1 trafficlight1;

printf("The size of TrafficLight1 %d\n", sizeof(trafficlight1));

return 0;
}

编译运行

直接输入make就可以了。

1
2
3
4
#beginner/struct/Makefile

struct6: struct6.c
gcc -o struct6 struct6.c

运行输出如下:

1
2
3
$ ./struct6
The size of TrafficLight 12
The size of TrafficLight1 4

扩展

既然位域指定了长度位,所以就涉及到万一赋值超过了会发生什么情况,可以通过给红绿灯赋一个大值看看。

比如复制一个2,那么会得到如下警告:

1
warning: implicit truncation from 'int' to bit-field changes value from 2 to 0 [-Wbitfield-constant-conversion]

编译有警告,不过还是生成了可执行文件,运行下看看结果吧。

C语言 共用体的访问

共用体的访问与结构体类似,也是有2中类型,我们只看看成员访问运算符.

所以按照通用的赋值方式,来看一下是否按照我们预定的方式运行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/*beginner/union/union2.c*/
#include <stdio.h>
#include <string.h>

union Data {
int i;
float f;
char str[20];
};

int main()
{
union Data data;

data.i = 123;
data.f = 456.0;
strcpy(data.str, "Hello World");

printf("data.i : %d\n", data.i);
printf("data.f : %f\n", data.f);
printf("data.str : %s\n", data.str);

return 0;
}

从结果上来看,what are you弄啥嘞,感觉什么跟什么呀,只有最后的字符串是正确的,这也就间接证明了共用体使用相同的存储空间,其他类型的赋值会破坏原先的赋值,正常情况下只有最后一次的赋值才会保证正确结果。

所以我们需要在每次赋值后直接查看结果,是可以保证结果正确的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*beginner/union/union3.c*/
#include <stdio.h>
#include <string.h>

union Data {
int i;
float f;
char str[20];
};

int main()
{
union Data data;

data.i = 123;
printf("data.i : %d\n", data.i);

data.f = 456.0;
printf("data.f : %f\n", data.f);

strcpy(data.str, "Hello World");
printf("data.str : %s\n", data.str);

return 0;
}

再次运行,可以看到结果就按照预想的进行了。

编译运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#beginner/union/Makefile
ALL : union1 union2 union3

union1: union1.c
gcc -o union1 union1.c

union2: union2.c
gcc -o union2 union2.c

union3: union3.c
gcc -o union3 union3.c

.PHONY : clean

clean:
rm -f union1 union2 union3

输出结果

1
2
3
4
5
6
7
8
9
$ ./union2
data.i : 1819043144
data.f : 1143139122437582505939828736.000000
data.str : Hello World

$ ./union3
data.i : 123
data.f : 456.000000
data.str : Hello World

C语言的关键字大全

C语言的短小精悍从它的关键字就能够看出来,作为C语言的关键字、保留字,是不能作为变量名、常量名或者其他标识符的,他们有自己独特的含义。

前面的章节中其实多多少少已经进行了讲解,先总结如下:

关键字 说明
auto 声明自动变量
break 跳出当前循环
case 开关语句分支
char 声明字符型变量或函数返回值类型
const 声明只读变量
continue 结束当前循环,开始下一轮循环
default 开关语句中的”其它”分支
do 循环语句的循环体
double 声明双精度浮点型变量或函数返回值类型
else 条件语句否定分支(与 if 连用)
enum 声明枚举类型
extern 声明变量或函数是在其它文件或本文件的其他位置定义
float 声明浮点型变量或函数返回值类型
for 循环语句
goto 无条件跳转语句
if 条件语句
int 声明整型变量或函数
long 声明长整型变量或函数返回值类型
register 声明寄存器变量
return 子程序返回语句(可以带参数,也可不带参数)
short 声明短整型变量或函数
signed 声明有符号类型变量或函数
sizeof 计算数据类型或变量长度(即所占字节数)
static 声明静态变量
struct 声明结构体类型
switch 用于开关语句
typedef 用以给数据类型取别名
unsigned 声明无符号类型变量或函数
union 声明共用体类型
void 声明函数无返回值或无参数,声明无类型指针
volatile 说明变量在程序执行中可被隐含地改变
while 循环语句的循环条件

C99 新增关键字

_Bool _Complex _Imaginary inline restrict

C11 新增关键字

_Alignas _Alignof _Atomic _Generic _Noreturn
_Static_assert _Thread_local

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。

什么意思呢,就是在同一块内存存储可以定义多个数据类型,但是在使用的时候,只有一个变量有效。

这里就有一个问题,变量有大有小呀,对的,所以这个时候共用体的空间为内部变量最大占用空间的值。

如此这般,共用体就可以通过共享存储空间,来避免当前没有被使用的变量所造成的存储空间的浪费。

共用体的成员可以使用任何数据类型,但是一个共用体所占用的存储空间的字节总数,必须保证至少足以能够容纳其占用空间字节数最大的成员。并且共用体每次只允许访问一个成员,也就是一种数据类型,确保按照正确的数据类型来访问共用体中的数据,就是你的责任了。

先看看union的格式:

1
2
3
4
5
6
7
union [tag]
{
member definition;
member definition;
...
member definition;
} [variables];

其中:

  • union为类型变量;
  • tag为共用体的标记;
  • member definition为变量的定义;

举个例子:

1
2
3
4
5
6
7
union test
{
int i;
float f;
double d;
char str[20];
} data;

通过这个例子可以看到,这个结构体的大小是多少呢?可以通过程序来确认一下。

OK,这次我们来聊聊结构体。

任务来了,我想让你给学生建立一个数据库,该怎么来做。

这个学生包含的信息如下:

  • ID:也就是学号,唯一区别码,用整型表示
  • Name:姓名,用字符串表示
  • Age:年龄,用整型表示
  • Sex:性别,用字符串表示

按照目前学过的知识我们的代码如下,比如先来一个李雷同学的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>

union test
{
int i;
float f;
double d;
char str[20];
};

int main( )
{
union test data;

printf( "data size : %d\n", sizeof(data));

return 0;
}

C语言的共用体union

共用体是一种特殊的数据类型,允许您在相同的内存位置存储不同的数据类型。

什么意思呢,就是在同一块内存存储可以定义多个数据类型,但是在使用的时候,只有一个变量有效。

这里就有一个问题,变量有大有小呀,对的,所以这个时候共用体的空间为内部变量最大占用空间的值。

如此这般,共用体就可以通过共享存储空间,来避免当前没有被使用的变量所造成的存储空间的浪费。

共用体的成员可以使用任何数据类型,但是一个共用体所占用的存储空间的字节总数,必须保证至少足以能够容纳其占用空间字节数最大的成员。并且共用体每次只允许访问一个成员,也就是一种数据类型,确保按照正确的数据类型来访问共用体中的数据,就是你的责任了。

先看看union的格式:

1
2
3
4
5
6
7
union [tag]
{
member definition;
member definition;
...
member definition;
} [variables];

其中:

  • union为类型变量;
  • tag为共用体的标记;
  • member definition为变量的定义;

举个例子:

1
2
3
4
5
6
7
union test
{
int i;
float f;
double d;
char str[20];
} data;

通过这个例子可以看到,这个结构体的大小是多少呢?可以通过程序来确认一下。

OK,这次我们来聊聊结构体。

任务来了,我想让你给学生建立一个数据库,该怎么来做。

这个学生包含的信息如下:

  • ID:也就是学号,唯一区别码,用整型表示
  • Name:姓名,用字符串表示
  • Age:年龄,用整型表示
  • Sex:性别,用字符串表示

按照目前学过的知识我们的代码如下,比如先来一个李雷同学的吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <string.h>

union test
{
int i;
float f;
double d;
char str[20];
};

int main( )
{
union test data;

printf( "data size : %d\n", sizeof(data));

return 0;
}

C语言的结构体

前面说到了结构体的使用方法和访问方法。

这一次讲点高级的结构体用法。

从前面的说法可以看到每次初始的方法为:

1
2
3
4
5
6
7
8
9
struct Student
{
int id;
char name[15];
int age;
char sex[10];
};

struct Student lilei = {1, "Li Lei", 18, "male"};

是不是感觉很啰嗦,是的,每次都要使用struct关键字来新定义一个变量,程序员最有名的特点就是懒,所以这问题必须解决,这就是关键字typedef的妙用了。

typedef的作用就是用来创建新类型,看看用法:

1
2
3
4
5
6
7
8
9
typedef struct 
{
int id;
char name[15];
int age;
char sex[10];
}Student;

Student lilei = {1, "Li Lei", 18, "male"};

是的,只需要在struct前面加上typedef这个关键字,把tag移到结构体的最后,以后初始化的时候,只需要把Student当做一个变量类型就可以了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/*beginner/struct/struct5.c*/
#include <stdio.h>

int main()
{

typedef struct
{
int id;
char name[15];
int age;
char sex[10];
} Student;

Student lilei = {1, "Li Lei", 18, "male"};
Student hanmeimei = {2, "Han Meimei", 17, "female"};
Student weihua1 = {3, "Wei Hua", 18, "male"};
Student *weihua = &weihua1;

printf("The student information :\n");
printf("ID \t | Name \t| Age\t| Sex \n");
printf("%d \t | %s \t| %d \t| %s \n", lilei.id, lilei.name, lilei.age, lilei.sex);
printf("%d \t | %s \t| %d \t| %s \n", hanmeimei.id, hanmeimei.name, hanmeimei.age, hanmeimei.sex);
printf("%d \t | %s \t| %d \t| %s \n", weihua->id, weihua->name, weihua->age, weihua->sex);

return 0;
}

编译运行

直接输入make就可以了。

1
gcc -o struct5 struct5.c

运行输出如下:

1
2
3
4
5
6
$  ./struct5
The student information :
ID | Name | Age | Sex
1 | Li Lei | 18 | male
2 | Han Meimei | 17 | female
3 | Wei Hua | 18 | male