扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
C99之后可以用const int
来表示常量,初始化后不能再被赋值。
整数和整数运算只会得到整数。当有浮点数参与运算的时候,就会变成浮点数。
整数用int
(输入输出都是%d
),浮点数double
(输入%lf
,输出%f
)。
运算符优先级:
优先级 | 运算符号 | 运算意义 | 结合关系 |
---|---|---|---|
1 | + - | 正、负 | 自右向左 |
2 | * / % | 乘、除、取余 | 自左向右 |
3 | + - | 加、减 | 自左向右 |
4 | = | 赋值 | 自右向左 |
=
赋值语句,也是运算,也是有结果的。
比如a=b=6
在C语言中,自右向左运算a=(b=6)
,意义是:b=6
的结果是6
,再赋给a
。
虽然可以“嵌套式赋值”(比如int c = 1 + (b=a)
),但不建议这么做。
调试运行的时候,下一步和单步进入,是有区别的。
复合赋值运算符:+=
、-=
、*=
、/=
。
递增递减运算符:++
、–
。
表达式 | 运算 | 表达式的值 |
---|---|---|
count++ | 给count加1 | count原来的值 |
++count | 给count加1 | count+1的值 |
count- - | 给count减1 | count原来的值 |
- -count | 给count减1 | count-1的值 |
scanf("%d,%d")
,scanf("%d %d")
,scanf("%d, %d")
、scanf("%d %d\n")
这四种方式是不同的。scanf()
函数用于接收输入进来的东西,返回值是接收了几个东西。
对于空格和回车,会等待后续输入。
对于其他非%d
数据,scanf()
函数接收失败,返回成功接收的个数,但会继续执行下一条语句。
%x
为16进制的输入或输出,%o
为8进制的输入或输出。
取余运算的应用:number%10
运算可提取出number
的个位数字。
辗转相除法、判断素数等,都用到了取余符号。
关系运算(<、<=、>、>=、==、!=):自左向右运算,且==
和!=
的运算级低。
语句a==b==6
,实际上就是判断1==6
,最终结果是0
。
语句6>5>4
,实际上就是判断1>4
,最终结果是0
。
if
语句中,if(条件){语句}
,注意分号和大括号。
switch
语句中,case
只是入口。如果不break
,语句会继续执行。
do…while
循环,while
括号里的都是循环的条件(虽和直觉不符。但和while
循环保持了一致)。
continue
和break
的差别,continue
跳出当前循环,break
跳出整个循环。
goto
语句在多层循环嵌套时候使用,其他场合不建议使用。
sizeof()
函数是静态函数,比如sizeof(a++)
语句编译后,a++
不执行。
&&
与运算会屏蔽0
后的语句、||
或运算会屏蔽1
后的语句。
补码=反码+1。
浮点数是有精度的。判断两个浮点数是否相等,应采用fabs(f1-f2)<=1e-12
。
char
型变量其实是一种特殊的int
类型:int a = 49和char c ='A'
,
按照%d
输出时,输出的都是49
,
按照%c
输出时,输出的都是'1'
。
综上,a+'a'-'A'
或a+'A'-'a'
就可以进行大小写转换。
逗号运算符,
,主要是用在for
语句里用。
C语言调用函数时,是值传递。
本地变量、局部变量:定义在大括号(块)里。
生存期、作用域:大括号(块)里。
块里定义的变量,块外无效。
块外定义的便令,块里也有效。
如果块里定义了与块外同名变量,屏蔽掉块外的变量。
同一个c文件里,函数声明和定义的头,必须一致,否则会编译出错。
f(a,b)
和f( (a,b) )
不一样,前者传了2个参数,后者传了1个参数。
函数里面可以声明另一个函数,但不能定义另一个函数。
C99之前,数组元素数量必须是编译时刻确定了的字面量。
数组一旦创建,长度固定。
数组创建后,最好要初始化。数组的访问,不能越界(即超过大下标)。
数组的集成初始化:int a[]={1,2,3,4,5,6};
。int a[]={[1]=2,4,[5]=6};
。
数组长度算法:sizeof(a)/sizeof(a[0]);
。
数组变量不能互相赋值:int b[] = a;
这种写法是错误的。只能遍历数组,每个元素依次赋值。
数组作为参数传入函数时,应把数组长度一同传入。
原因:数组作为参数时,传入的只是地址。
unix系统下,可以使用man sqrt
命令,查阅sqrt
函数。
求素数的方法:方法一 顺序往下挑选、方法二 剔除已有素数的倍数。
二维数组,列数不可以省略,行数可以。int a[][5] = {{1,2,3,4,5},{6,7,8,9,0}};
。
运算符&
:对变量取地址,不能是表达式,如&(i+p)
或&(i++)
等做法是错误的。
printf()
函数里用%p
来输出地址。
对于一个数组a[]
来说,a
和&a
和&a[0]
都是相同的,都是a
的首地址。
作为参数的指针,在函数里面可以通过这个指针访问外面这个i
。
如果只是传值,那么调用的函数无法改变外面的i
。
但是传了i
的地址,那么就能对地址里面的东西进行改动,也就是把i
进行改动。
赋值号的左边不一定是个变量,也有可能是个运算表达式的结果,这就叫左值。*p
就是p取内容的运算,但是可以被赋值,所以*p
是左值。a[0]
就是a
取0位置内容的运算,但是可以被赋值,所以a[0]
是左值。
指针的应用,使得被调用的函数能够更改原来函数的变量!
一般情况下,函数使用return
返回运行状态,使用指针返回值。
指针必须进行初始化,即指向某个变量的地址。否则将会是一个“野指针”,很危险。
传数组,实际上就是在传地址。所以说,被调用的函数可以改外部函数的数组元素!
但sizeof(a)
在内外却不等,因为外面的sizeof(a)
求的是整个数组地址所占长度,里面的sizeof(a)
求的是首地址的长度。
数组变量本身表达地址,因此int *p = a;
无需用&
来取地址。
数组名就是地址指针,因此a
等价于&a[0]
。[]
运算符可以对数组做,也可以对指针做:p[0]
等价于a[0]
。*
运算符也可以对数组做:*a
。
实质:数组就是常量指针,所以不能被赋值。int a[]
等价于int * const a
。
指针与const
int i;
const int* p1 = &i; // *p1是const,即不能通过*p1修改i
int const* p2 = &i; // *p2是const,即不能通过*p2修改i
int *const p3 = &i; // p3是const,即p3只能指向i,等价于数组
void f(const int* x);
,表示f()
不会改动传来的指针的指向的值。
const数组:const int a[]={1,2,3,4,5,6,}
,这样的话,内部的函数不会修改外部数组了。
指针p
、q
可以递增/递减,即进行p++
、p--
、p-q
运算。p=p+1
,实际上指的是,将p
指到下一个单元。
而数组实际上就是特殊的指针,因此:*p
等价于a[0]
,*(p+1)
等价于a[1]
。
*p++
:取出p的东西,顺便把指针向后移动一次。举例:
for( int i=0; i< sizeof(a)/sizeof(a[0]) ; i++ ){printf("%d\n",a[i]);
}
// 可以改写成:
char *p = a[];
while( *p != -1 ){printf("%d\n", *p++);
}
指针是可以比较的,比如数组中的单元的地址就是线性递增的。
不建议使用0
地址,但可以初始化为NULL
。
类型不同的指针不能互相赋值。除非进行强制类型转换。
void*
表示不知道指向什么东西的指针。经常在比较底层的情况下应用到。
在C99之前(ANSI C)时,如何用变量定义数组的大小呢?
使用stdlib.h
中的函数malloc()
。int *a = (int*) malloc(n*sizeof(int));
原因:malloc()
函数申请到的是一块(void*)
类型的地址空间,以字节为单位,需要强制类型转化为(int*)
。
接下来,把a
当作数组来用。如a[0]
。
最后,用了malloc()
就要释放掉,即free(a)
。
但是系统空间是有限的,malloc()
不一定总能成功的申请到需要的空间。malloc()
返回值为1代表申请成功,返回值为0代表申请失败。
free()
函数只能释放掉malloc()
来的地址。
常见问题:
(1)malloc了没free,会导致运行内存逐渐下降。
(2)free过了再去free。
(3)地址变了,直接去free。
字符串以'\0'
结束,但它不是字符串的一部分。计算这个字符串长度的时候不包括这个'\0'
。
字符串以数组形式存在,以指针形式访问。
因此,可以通过遍历数组的方式来遍历字符串。
对字符串进行操作时,需要#include
。
字符串的定义形式:
由双引号括起来的东西Hello
,叫字符串的字面量.
字符数组长度为6,因为结尾还有'\0'
。
char *str = "Hello";
char word[] = "Hello";
char line[10] = "Hello";
char *s = "Hello, world!"
中:
这种写法,会把"Hello, world!"
写在常量空间里,实际上s
是const char *s
,不允许写入操作,赋值只能靠初始化。s
是个指针,初始化为指向一个字符串常量。
试图对s
所指的字符串做写入会导致严重后果。
char s[] = "Hello, world!"
中:
这种写法,会把"Hello, world!"
写在变量空间里。
在程序运行结束后,会被自动回收。
这两种写法如何选择?
数组形式:作为本地变量,空间自动回收。
指针形式:只读、处理参数、动态分配空间。
如果要处理一个字符串,用数组。
如果要构造一个字符串,用指针。
char*
不一定是字符串,它本意是“指向char型单个变量的指针”。
符串赋值,实质上是让指针s
指向了指针t
所指的字符串,任何对s
的操作就是对t
做的,因为对于字符串,赋值符号赋的是地址。
char *t = "Hello";
char *s;
s = t;
使用scanf()
来读字符串时,读到空格、tab、回车为止。scanf("%7s",string)
;表示读入字符串最多7位。
常见错误:
char * string;
scanf("%s", string);
// 指针string很有可能指向了一个危险的地方。
// 改正方法:
// 1. 赋初值,但以后无法更改
// 2. 数组形式定义字符串变量
char buffer[100] = "";
// 这是一个空字符串,buffer[0]=='\0'
char buffer[] = "";
// 这个数组长度只有1,放不下任何东西
char **a
,char a[][10]
,char *a[]
,这三者是不同的。char **a
:表示a
是一个指针,指向另一个指针,那个指针指向一个字符(串)char a[][10]
:表示a
是一个二维数组变量,每一个元素都是一个含有10个元素的一维数组。char *a[]
:表示a
是一个存放指针的一维数组,每个指针都指向一个char
型变量。
main(int argc, char const *argv[])
这里的*argv[]
表示:main()
函数调用的参数。
常常在命令行中使用到。
int putchar(int c)
函数:返回写了几个字符,EOF(-1)
表示写失败。
int getchar(void)
函数:返回类型是int
,是为了返回EOF(-1)
。
windows环境下Ctrl+Z
表示输入结束;Unix环境下Ctrl+D
表示输入结束。
通过下面的例子,可以感受到 shell 的存在和工作机制。
输入设备和用户程序之间有缓冲区,这个缓冲区会在不同编译环境下有不同的工作机制。
另外,搭建 VScode的 C++ 开发环境时,win 下使用 powershell 作为终端,ubuntu下默认的终端。
这二者对于cout
函数的处理是不一样的。
前者cout
输出不一定要有endl
,后者cout
输出一定要有endl
才会输出。
int main( int argc, char const *argv[])
{int ch;
while ( ( ch = getchar() ) != EOF ){putchar( ch ) ;
}
printf("EOF\n");
return 0;
// getchar() 是用 int 型变量来接收 char。
// putchar() 是把 int 型变量输出为 char。
// 这段代码,输入什么就输出什么
// 按下Ctrl+C会停止程序
// 输入Ctrl+Z会输出EOF
}
字符串头文件string.h
里常用的函数:strlen()
:
形式:size_t strlen(const char *s
功能:返回s
的字符串长度(不包括结尾的\0
)
备注:一般情况下,等于sizeof()-1。
字符串头文件string.h
里常用的函数:strcmp()
:
形式:int strcmp(const char *s1, const char *s2)
功能:比较两个字符串并返回0、正数、负数。0表示相等,正数表示s1
大,负数表示s2
大
比较原则:从第一个字符开始比较,相同则继续,不同则相减。任一串读完则跳出。
备注:s1==s2
是warning而非error,因为s1==s2
比较的是地址,而非字符串内容。
字符串头文件string.h
里常用的函数:strcpy()
:
形式:char *strcpy(char *restrict dst, const char *restrict src)
:
功能:把src
的字符串拷贝到dst
,并返回dst
(最后的\0
也拷贝)
备注:restrict
表示这两个字符串在存储上不能重叠。
复制字符串的套路:
char *dst = (char*)malloc (strlen(src)+1);
// 不能用sizeof,src还有可能是指针、数组
// malloc后最后要free
strcpy(dst,src);
字符串头文件string.h
里常用的函数:strcat()
:
形式:char *strcat(char *restrict s1, const char *restrict s2)
功能:把s2
拷贝到s1
后面,接成一个长的字符串,返回s1
原理:把s2
的第一个char
写在s1
的\0
的位置
备注:s1
必须有足够的空间
strcpy()
和strcat()
的安全问题:如果目的地没有足够空间会产生安全问题。
因此,尽量使用安全版本。char *strcpy(char *restrict dst, const char *restrict src, size_t n)
char *strcat(char *restrict s1, const char *restrict s2, size_t n)
int strcmp(const char *s1, const char *s2, size_t n)
其中,strcmp()
函数的n
表示,只比较前n个字符。
字符串中找字符函数strchr()
,分为从左和从右两个版本。
利用字符串找字符函数,可以截取字符串中的某段。
字符串中找字符串函数strstr()
,分为不忽略和忽略大小写两个版本。
enum COLOR {RED, YELLOW, BLUE, NumCOLOR};
enum COLOR
,实际上就是int
,可以用%d
进行输出。enum COLOR {RED=1, YELLOW, BLUE=5};
结构体声明、定义一个结构体变量时,最好放在main()
函数外边。
结尾一定要记得写分号;
// 方式一:先声明,在定义。结构体类型名:struct date。
struct date{int month;
int day;
};
struct date today, tommorow;
// 方式二:直接定义。结构体类型没有名字。
struct{int month;
int day;
} today, tommorow;
//方式三:同时声明和定义变量。结构体类型名:struct date。
struct date{int month;
int day;
} today, tommorow;
结构变量的初始化:struct date today = {.month=4, .day=19}
结构变量的访问:today.month
结构变量的赋值:
方式一(成员分别赋值):today = (struct date){4, 19};
方式二(变量整体赋值):today = tomorrow;
结构变量的名字就是变量本身,并非地址(与数组和字符串不同)。
因此对结构变量取地址,&today
。
当然也可以对结构变量的成员取地址,&today.month
结构体是可以作为函数参数、返回值的。
结构体不能直接printf和scanf,但可以自己做类似的函数。
编写时,最好使用指针方式。
指向结构体的指针,最好使用->
:
struct date *ptoday = &today;
(*ptoday).month = 12;
ptoday->month = 12;
视频11.2 结构与函数在15分22秒处的例子,把结构体指针做为参数,又把结构体指针作为函数返回值。因此各个函数之间可以互相调用、赋值。
结构体中有数组:直接套就行了。
数组中有结构体:直接套就行了。
结构体中有结构体:直接套就行了。
但注意:成员是变量时用.
,成员是指针时用->
。
普通的数据类型:typedef int Length;
定义结构体:
typedef struct ADate{int month;
int day;
} Date;
// 或者省略`ADate`:
typedef struct{int month;
int day;
} Date;
// 定义变量时,可以直接写
Date today = {4, 19};
表面上看起来和struct一样,但实际上所有成员占用相同的内存空间。
如果发生重叠,会“冲掉”原来的值。
union AnElt{int i;
char c[4];
} elt1;
// 当然此处也可以用typedef
elt1.c[0] = 'a';
elt1.i = 0xDEADBEEF;
// 此时,后填写的`i`会把先填写的`c`冲掉
本例中union的作用:查看int变量中每个字节的情况。
当然,x86的CPU采用小端形式进行存储数据。
因此,如果对上边的c
数组进行遍历读取的话,会得到:c[0]
等于0xEF
,c[1]
等于0xBE
,c[2]
等于0xAD
,c[3]
等于0xDE
。
定义在函数外边的变量,就是全局变量。
字符串__func__
,输出时自动变为当前函数名。
int main(){printf("%s", __func__);
return 0;
}
全局变量在没有初始化时,会得到0
值(指针得到NULL
)。
全局变量必须使用已知值来初始化。
不能将一个全局变量赋值给另一个全局变量。
本地变量会屏蔽外部的全局变量。
静态本地变量,本质上就是全局变量。
原因:在程序进行编译时,紧挨着放在放在同一块区域中。static int i = 1;
函数第二次被调用时会保留函数第一次执行后的值。
总结:
本地变量:本地作用域,本地生存期。
全局变量:全局作用域,全局生存期。
静态本地变量:本地作用域,全局生存期。
函数返回指针,不能返回本地变量的地址。
过多使用全局变量和静态本地变量,在多线程时是不安全的。
因此,应尽量不要使用全局变量和静态本地变量。
#
开头的那一行。#define PI 3.14159
,其中PI
是宏的名字,3.14159
是宏的值。#define
:只是文本的替换,经常需要加括号()
。#define
:可以使用//
进行注释。#define
:可以定义没有值的宏。__DATE__
、__TIME__
等等。#define cube(x) ((x)*(x)*(x))
。#define MIN(a,b) ((a)>(b)?(b),(a))
;
。将各个函数分开放在不同的(.c
)文件中。
编译(compile):对单个源代码文件的编译。
构建(build):对整个项目做链接。
如果没有头文件(.h
),编译器遇到main()
里陌生的函数会乱猜。
因此,头文件里包括了函数的声明,用来作为不同源文件之间的桥梁。
#include
用来插入对应的头文件中的文本内容。
#include
后的<>
用于自带头文件,""
用于用户头文件。
一般来说,任何c文件都有对应的h文件。
头文件中还包括了,在源文件中的全局变量的声明。extern int AllVariable;
头文件中,建议只放声明。
同一个编译单元里,同名的结构不能被重复声明。
因此,有标准头文件结构。
#ifnedf _MAX_H_
#define _MAX_H_
#endif
你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流