C语言基础复习总结

大一学的C++,不过后来一直没用,大多还给老师了,最近看传智李明杰老师的ios课程的C语言入门部分,用了一周,每晚上看大概两小时左右,效果真是顶一学期的课,也许是因为有开发经验吧,废话少说,直接把总结贴出来了~

#include <stdio.h>

int main(int argc, const char * argv[])

{

printf("Hello, World!\n");

return 0;

}

#include是预处理指令,在编译前把尖括号里的内容原封不动地拷贝到对应位置。.h是头文件,里面是库函数的声明(不是实现)。尖括号表明是系统自带的,会去系统目录找,双引号是自己的文件,会先在源程序当前目录找,找不到就去操作系统的path路径找,还找不到才去C函数库里找。

包含关系允许嵌套包含但是不允许递归包含(死循环)。

C语言语法不严格,main函数可以不写返回值,默认返回int,参数可以不要,可以不return。

C语言程序运行的过程:

1.把源代码翻译成目标代码。Xcode是64位编译器。编译成功后生成同名.obj文件(多个c文件对应多个obj文件)。

2.把C语言源文件之间的调用依赖以及C语言函数库链接进来,成为可执行的机器代码。在xcode下生成的是unix可执行文件。

在java当中方法定义没有顺序限制,但是在标准c当中只能后面的函数调用前面的,因为C是从上往下编译的,如果想放在前面,需要声明,声明可以省略参数名,只要类型:

#include <stdio.h>

int sum(int, int);

int main(int argc, const char * argv[])

{

int c = sum(10,3);

printf("%d\n", c);

return 0;

}

int sum(int a, int b)

{

return a+b;

}

一般来说,会把函数声明和定义放在不同文件当中,比如把sum函数的声明放在test.h里,实现放在test.c里,然后在main函数之前引入:

#include "test.h"

反复引用同一个文件是没关系的,可以使用预编译指令做检查机制。但是不要导入.c文件,以免在链接时出现函数重复而报错,C不是面向对象的,函数名不能重复。

printf输出需要使用百分号占位符,.2f是保留两位小数,不是四舍五入。

// My age is 26, height is 1.55, name is 李明杰,sex is 'A'

printf("My age is %d, height is %.2f, name is %s,sex is ‘%c’\n", 26,1.55f,"李明杰",'A');

scanf是阻塞性的函数,等待标准设备输入,需要传变量的地址。

#include <stdio.h>

int main(int argc, const char * argv[])

{

printf("请输入两个整数,用逗号隔开:");

int a,b;

scanf("%d,%d", &a, &b);  //传a的地址

printf("%d\n", a+b);

return 0;

}

C语言的类型分四类,一种是空类型viod,一种是int,float,double,char,一种是构造类型,比如数组,struct,union(基本没用),enum,另一种是指针类型void*。C是强类型语言,指定类型是为了分配适当大小的空间。Char类型不论多少位的编译器,都占一个字节。

C与java不同,局部变量没有初始化使用也不会报错,但默认值不一定是0,是随机数,所以不要不初始化。全局变量则会被默认初始化。

Char类型范围是-128到127,最好不用ascii码的值,直接用’a’,它不是unicode的,也不支持字符串。

类型修饰符:short,long,signed,unsigned,最常用的是修饰int,被修饰的int可以省略,比如只写long,与java不同,这并不代表这是long类型,而是int。不管什么编译器,int至少2个字节。

C语言里没有boolean,关系判断返回1或0的int值,没有-1!任何非0值都位真,只有0才是假,比如if(9)为真。

C语言可以用逗号连接多个表达式,它的返回值是最后一个表达式的值,下面的代码输出12。

#include <stdio.h>

int main(int argc, const char * argv[])

{

int a=9;

int b=10;

int c;

c = (a=a+1,b=3*4);

printf("%d",c);

}

对于一个变量,变量存储单元的“第一个字节”的地址就是该变量的地址,取地址用&,返回数字,习惯用16进制。

#include <stdio.h>

int main(int argc, const char * argv[])

{

char a = 'A';

int b = 66;

printf("%x\n", &a);

printf("%x", &b);

}

数组用来存放“同一种”类型的变量,不能用变量做长度,应该用常量,但是xcode不会报错。系统为数组分配的空间是连续的。

int ages[5];

printf("%d",sizeof(ages));

得到的长度是20,C语言数组名就代表数组地址,所以ages就是个常量,不能赋值。取数组地址的方法有:

printf("%d\n",&ages[0]);

printf("%d\n",ages);

数组可以初始化,比如

int a[2] = {8, 10};

放在后面的元素可以省略,但是可读性不好,比如

int a[2] = {8, };

如果后面已经定义了全部元素则长度可省略,比如

int a[] = {8, 10};

C语言传参是传值,但是传数组(指针)的话则不是,实际传的是地址,所以内容会被改变。

#include <stdio.h>

//接受数组参数,可以不写长度

void test(int array[])

{

array[0] = 9;

}

int main(int argc, const char * argv[])

{

int a[3];

a[0] = 10;

printf("%d\n",a[0]);

test(a);    //数组名代表地址,传的是指针

printf("%d\n",a[0]);

}

二维数组是一维数组的集合,是由一维数组组成的一维数组。它在内存中是按行存储的,比如a[0][0]->a[0][1]->a[0][2]->a[1][0]。

取地址的方式有a,a[0],&a[0][0]。

初始化可以按行:

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

也可以都写出来:

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

可以部分省略,默认为0:

int a[2][3] = {{1,,3},{4}};

可以省略行数,不能省略列数:

int a[][3] = {1,2,3,4,5};

C语言没有String类型,多个字符用字符数组存储,为了和普通字符数组区分,字符串数组用’\0’结尾,必须写,否则可能会内存溢出,它是一个ascii码为0的字符,是空操作符,表示什么也不干,所以“mj”的长度是3,不是2。通常用下面第二种初始化:

char s1[] = { 'm','j','\0' };

char s2[] = "mj";

打印的方式:

printf("%s\n", s2);

puts(s2);

放在尾部\0是因为输出过程会从字符串地址开始向后找第一个\0,所以必须要有结尾,没有结尾就会一直找下去,输出错乱的东西。

字符串输入的过程会自动在尾巴加\0,例子:

char s[20];

scanf("%s", s); //s就是地址,不需要&s

printf("%s", s);

但是gets是不安全的,原理同上,会把另一个字符串s2里的内容冲掉,例如:

char s2[] = "mj";

char s1[2];

gets(s1);

printf("%s\n", s1);

printf("%s\n", s2);

字符串本身就是数组,如果要存储多个字符串,则需要使用二维数组,比如char names[15][20]表示可以存15个名字。代码:

char names[2][20] = {{"jay"},{"jim"}};

字符串处理的两个方法:

//输出到控制台

putchar('a');

//等待用户输入

char c;

c = getchar();

字符串处理函数声明在string.h当中。

测量字符串的字符长度:

int len = strlen("李明杰");

printf("%d\n", len);

输出9,strlen返回字符串的字符数,不是长度,中文是3个字符。

字符串拷贝:

char left[10];

strcpy(left, "itcast");

printf("%s", left);

从右边的常量拷贝给左边的变量并且自动加\0。

拼接字符串:

char left[10] = {'m','j','\0'};

strcat(left, "ios");

printf("%s", left);

把右边的字符串,接在左边的后面,会去掉左边的“第一个”\0,但是要保证左边字符串的长度足够,否则会内存溢出。

字符串比较:

int delta = strcmp("abc","ABC");

printf("%d", delta);

返回左边减右边的差,一位一位地比ascii码,\0就是0.

指针变量用来保存一个特定类型的变量的地址,如:

char a;

char *b = &a;

*b = 'a';

printf("%c",a);

b是一个char*类变量指向a的地址,第三行*b当中的*是指针运算符,*b表示访问b的值(a的地址)所对应的存储空间。

指针类型所占用的空间只和编译器有关。

其中第二句可以拆分成两句:

char *b;

b = &a;

第二句不能写*b,*是访问符。

指针操作的两个错误:

//错误1:不要直接使用未分配的指针

char *p;

*p=10;

//错误2:不要给指针变量直接赋地址

p = 100;

交换a和b的例子:

void swap(int *v1, int *v2)

{

int temp = *v1;

*v1 = *v2;

*v2 = temp;

}

int main(int argc, const char * argv[])

{

int a = 10;

int b = 9;

swap(&a,&b);

printf("%d %d\n",a,b);

}

因为默认会传临时变量,所以要传地址,参数表用指针类型,调用的时候就要传地址进去。交换的时候也要用星号来取指针变量里的地址对应的值。

用指针可以实现函数多返回值,类似c#的out参数。比如:

int sumAndMinus(int v1, int v2, int *p) {

*p = v1 - v2;

return v1 + v2;

}

int main(int argc, const char * argv[])

{

int a = 10;

int b = 4;

int sum;

int minus;

sum = sumAndMinus(a,b,&minus);

printf("%d %d",sum,minus);

}

数组的名字就是它的地址,所以把指针指向数组的时候,后面两行代码是等价的:

int a[2];

int *p;

p = &a[0];

p = a;

可以用指针遍历数组,如下:

int a[3] = {1,2,3};

int *p = a;

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

printf("a[%d]=%d\n",i,*(p+i));

}

对于指针变量来说,p+i当中的i要看p指向的类型,比如指向一个两个字节的类型,i就是两个字节,不是纯粹加一个数字。上面的做法不会改变p所指向的内容,但是如果写*(p++)则会改。

另外,既然数组名就是p所指的,所以*(a+i)也可以,但是*(a++)不可以,数组的首地址是常量不能改。

像下面这样也可以,只是p最终位置改变了:

int *p = a;

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

printf("a[%d]=%d\n",i,*p);

}

如果参数表是数组,那么传数组名或指针都可以:

void change(char c[]) {

c[0] = 1;

}

int main(int argc, const char * argv[])

{

char a[3];

change(a);

}

如果参数表是指针,那么也可以传指针:

void change(char *c) {

*c = 1;

}

int main(int argc, const char * argv[])

{

char a[3];

change(a);

printf("%d\n", a[0]);

}

总之,如果形参是数组或指针,则可以传递数组名或指针。

对于一个字符串,遍历的方式有:

char s[7] = "itcast";

for (int i=0; s[i]!='\0'; i++) {

printf("%c\n", s[i]);

}

比较好理解的方法是:

char *p = "itcast";

for (; *p!='\0'; p++) {

printf("%c\n", *p);

}

第一种方式利用数组定义的是字符串变量,但是第二种方式用指针定义的是字符串常量,所以,第二种方法一旦用下面的方式来改写就错了:

char *p = "lmj";

*p = 'f';

总之,char a[] = “lmj”是变量,char *p = “lmj”是常量,严格来说前面应该加上const。

一个函数可以返回一个指针,比如:

char * test() {

return "itcast";

}

函数的名称就代表函数的地址,可以定义指向函数的指针,比如:

#include <stdio.h>

int sum(int a, int b) {

return a+b;

}

int main(int argc, const char * argv[])

{

//定义一个特定返回值和参数表的指针p

//需要占位的是函数名,并且指向sum函数

int (*p)(int, int);

p = sum;

//利用指针变量p取出所指的函数,间接调用

int result = (*p)(1,2);

//也可以直接调用

int result2 = p(5,6);

printf("%d %d", result,result2);

}

可以把函数指针当做参数来使用,类似c#当中传递lambda表达式:

#include <stdio.h>

int calculate(int a, int b, int (*p)(int,int)) {

return p(a,b);

}

int sum(int a,int b){

return a+b;

}

int main(int argc, const char * argv[])

{

int result = calculate(1,2, sum);

printf("%d", result);

}

预处理指令以#开头,是在编译之前执行的,三种常用的预处理指令分别是宏定义、文件包含和条件编译。

宏定义的功能是字符串替换,通常用来定义常量:

#define NUM 6

int main(int argc, const char * argv[])

{

int a[NUM] = {1,2,3,4,5,6};

}

也可以使用带有参数的宏定义:

#define mul(a,b) ((a)*(b))

int main(int argc, const char * argv[])

{

int a = mul(1,2);

printf("%d",a);

}

定义带有参数的宏最好把参数带上括号,因为它的实质是字符串替换。最外层最好也加一个括号,因为宏替换之后,它的整体还会和其他代码进行数学运算。

宏定义没有内存检测,纯粹是字符串替换,所以一些简单的计算,执行起来性能比函数要好。

条件编译指,某段代码,只让它在满足某种条件时才编译。

#include <stdio.h>

#define NUM 10

int main(int argc, const char * argv[])

{

#if NUM > 0

printf("NUM大于0");

#elif NUM == 0

printf("NUM等于0");

#else

printf("NUM小于0");

#endif

return 0;

}

注意预处理指令里的宏NUM只能用预处理指令里定义的。

另外,可以根据有没有定义过宏来判断:

#define NUM 10

int main(int argc, const char * argv[])

{

#ifdef NUM

printf("定义了");

#endif

#ifndef NUM

pringf("没定义");

#endif

return 0;

}

C的变量有不同的存储类型、生命周期和作用域。

局部变量只在函数内有效。全局变量被其他函数共享,从定义的位置到源代码结尾有效。

存储类型:有三个地方可以存变量:运行时堆栈、普通内存和硬件寄存器,它决定了变量的生命周期。

三个地方分别对应自动变量、静态变量和寄存器变量。

被关键字auto修饰的“局部”变量是自动变量,默认所有局部变量都是自动变量,基本不写auto。当执行到自动变量所在的函数时,自动变量会创建,离开函数时会销毁。

静态变量会在程序运行时创建,在程序结束后销毁。所有全局变量都是静态变量。另外有一种被static修饰的局部变量也是静态变量,这种修饰改变了它的生命周期,但是没改变作用域,它的创建时间也是在函数调用的时候。函数内的static变量可以在函数内部进行计数。

存储在硬件寄存器内的变量叫寄存器变量,它被register修饰,性能最高。只有自动变量才能存储在寄存器中,只限存储int、char和指针类型。如果寄存器已满,在运行时会自动转化为自动变量处理。寄存器变量通常是一些频繁使用的变量。寄存器变量的生命周期和自动变量一致。

允许被其他源文件调用的函数是外部函数,函数默认是外部函数,不允许有同名的外部函数,否则会有链接错误。

定义或者提前声明一个外部函数需要用extern,但是默认就是外部的,所以这个关键字一般不写。在C99标准当中,如果一个源文件里没有提前声明外部函数会报编译错误,但是xcode里不会出错。

不允许其他文件访问的函数是内部函数,不同源文件里允许有同名的内部函数。C的static和java中完全不一样,内部函数需要用static关键字修饰,这样在链接的时候其他源文件就无法找到这个static函数了。在同一个源文件中,声明一个static函数也需要加这个关键字。

C语言中一个函数不能使用在函数后面声明的变量,除非在函数前面声明,声明变量的关键字是extern,这个extern其实又可以省略:

extern int a;

int main(int argc, const char * argv[])

{

a = 10;

return 0;

}

int a;

在一个函数内,如果用extern声明了一个变量,那么它使用的还是外部的变量,extern本来就是外部的意思,证明:

#include <stdio.h>

void test();

int main(int argc, const char * argv[])

{

extern int a;

a=10;

test();

}

int a;

void test()

{

printf("%d", a);

}

在C语言中在不同的源文件当中存在同名的全局变量,则这些变量都代表同一个变量。允许被其他文件访问的变量叫外部变量,默认情况下定义的变量都是外部变量。想使用其他文件里的外部变量,需要在前面声明(extern可省)。注意,extern后面的变量,一定是声明,而不是定义!

用static修饰的全局变量叫做内部变量,外部访问不到这个变量(外部的extern声明语句无法看到内部的static变量)。如果说两个文件中的同名变量有一个或者都加了static,则它们不是一个变量。

结构体可以定义在方法内部或外部,如果在内部,则只能在函数内使用这个结构体:

void main(int argc, const char * argv[])

{

//定义结构体类型

struct Student {

int age;

char *name;

float height;

};

//定义结构体变量

struct Student stu = { 27, "mj", 180.3};

printf("%d", stu.age);

}

也可以同时定义结构体并定义变量赋值,在这种情况下结构体的名字可以省略,因为没有意义,类似java的匿名方法:

struct {

int age;

char *name;

float height;

} stu = { 27, "mj", 1.8f};

结构体内可以包含别的结构体,但是不允许包含自己导致递归。需要注意的是,结构体的定义和初始化要写成一句,如果拆成两句会报错。

可以定义指针指向结构体,通过指针有两种方式取结构体内的值:

struct Student stu = { 27, "mj", 180.3};

struct Student *p = &stu;

int a =(*p).age;

int b = p->age;

把结构体当做参数传给函数,传的是值,不是地址,传地址需要指针:

struct Student {

int age;

};

void change(struct Student* stu) {

stu->age = 1;

}

void main(int argc, const char * argv[])

{

struct Student stu = { 27, "mj", 180.3};

struct Student *p = &stu;

change(p);

printf("%d", stu.age);

}

C会把枚举当做整形常量,从0开始,定义枚举:

enum Season { spring, summer, autumn, winter };

enum Season s = spring;

也可以同时定义枚举变量并赋值,和结构体一样,枚举的名称也可以省略。

typedef关键字的作用是给数据类型定义一个别名,比如:

typedef int Integer;

typedef char* String;

void main(int argc, const char * argv[])

{

Integer a = 10;

String name = "itcast";

}

可以给结构体起别名,这样定义变量的时候就能省略struct关键字了,这时建议用匿名的,因为没用:

void main(int argc, const char * argv[])

{

typedef struct {

float x;

float y;

} CGPoint;

CGPoint p = {10, 10};

}

例子:给指向结构体的指针重命名,此时也可以匿名:

typedef struct Point{

float x;

float y;

} * PPoint;

struct Point point = {10,10};

PPoint pp = &point;

事实上最常用的定义结构体和枚举的方式是这样,表意非常明确——给一个匿名的结构体命名:

typedef struct {

float x;

float y;

} Point;

还可以给一个指向函数的指针一个别名:

int sum(int a, int b){

return a+b;

}

void main(int argc, const char * argv[])

{

typedef int (*SumPoint)(int,int);

SumPoint p = sum;

p(1,2);

}

在这里SumPoint就是别名,不用在后面再起别名。

不建议用宏定义来其别名,它纯粹是字符串替换,如果连续声明了两个变量就会有问题了。

 
 
分类: IOS开发

C语言基础复习总结的更多相关文章

  1. MySQL学习笔记_8_SQL语言基础复习

    SQL语言基础复习 一.概述 SQL语句注释方式 1)以"#"开头直到行尾的所有内容都是注释 2)以"--"(--后还有一个空格)开头直到行尾的所有内容都是注释 ...

  2. 2018.6.13 Java语言基础复习总结

    Java语言基础与面向对象编程实践 第一章 初识Java 1.1机器语言 机器语言是指一台计算机全部的指令集合.机器语言室友0和1组成的二进制数,是一串串由0和1组成的指令序列,可将这些指令序列交给计 ...

  3. ndk学习之c++语言基础复习----C++容器、类型转换、异常与文件流操作

    继续来复习C++,比较枯燥,但是这是扎实掌握NDK开发的必经之路,不容小觑. 容器: 容器,就是用来存放东西的盒子. 常用的数据结构包括:数组array, 链表list, 树tree, 栈stack, ...

  4. ndk学习之c++语言基础复习----面向对象编程

    关于面向对象编程对于一个java程序员那是再熟悉不过了,不过对于C++而言相对java还是有很多不同点的,所以全面复习一下. 类 C++ 在 C 语言的基础上增加了面向对象编程,C++ 支持面向对象程 ...

  5. ndk学习之C语言基础复习----虚拟内存布局与malloc申请

    在这一次中来学习一下C语言的内存布局,了解它之后就可以解释为啥在用malloc()申请的内存之后需要用memset()来对内存进行一下初始化了,首先来了解一下物理内存与虚拟内存: 物理内存:通过物理内 ...

  6. ndk学习之C语言基础复习----基本数据类型、数组

    关于NDK这个分类在N年前就已经创建了,但是一直木有系统的记录其学习过程,当然也没真正学会NDK的技术真谛,所以一直也是自己的一个遗憾,而如今对于Android程序员的要求也是越来越高,对于NDK也是 ...

  7. C语言基础复习:字符,字符数组,字符串,字符指针

    1. 概述2. 字符2.1 字符定义和大小2.2 字符的输入和输出2.3 字符的计算3. 字符数组3.1 字符数组的定义和大小3.2 字符数组的输入和输出3.3 字符数组的计算4. 字符串4.1 字符 ...

  8. ndk学习之C语言基础复习----指针、函数、预处理器

    指针: 指针乃C.C++的灵魂之所在,所以有必要好好的复习复习.什么是指针?一句话来概括:“指针是一个变量,它的值是一个地址.”,其中指针变量的声明有如下三种形式: 其中第一种是被推荐的写法. 其中还 ...

  9. ndk学习之c++语言基础复习----C++线程与智能指针

    线程 线程,有时被称为轻量进程,是程序执行的最小单元. C++11线程: 我们知道平常谈C++线程相关的东东基本都是基于之后要学习的posix相关的,其实在C++11有自己新式创建线程的方法,所以先来 ...

随机推荐

  1. cocos2d0基础知识三个音符

    1.触摸屏事件: bool HelloWorld::init() { //省略的代码的最后位 this->schedule(schedule_selector(HelloWorld::usecr ...

  2. 6天通吃树结构—— 第五天 Trie树

    原文:6天通吃树结构-- 第五天 Trie树 很有段时间没写此系列了,今天我们来说Trie树,Trie树的名字有很多,比如字典树,前缀树等等. 一:概念 下面我们有and,as,at,cn,com这些 ...

  3. 【高德地图API】从零开始学高德JS API(八)——地址解析与逆地址解析

    原文:[高德地图API]从零开始学高德JS API(八)——地址解析与逆地址解析 摘要:无论是百度LBS开放平台,还是高德LBS开放平台,其调用量最高的接口,必然是定位,其次就是地址解析了,又称为地理 ...

  4. wamp 已安装cakephp Fatal error: You must enable the intl extension to use CakePHP. in XXX

    今wamp已安装cakephp3.x什么时候.报告这样的错误:Fatal error: You must enable the intl extension to use CakePHP. in D: ...

  5. HDU Billboard

    题目分析:给你n张海报,一个宣传板.让你在满足海报能够贴在最高位置的时候则贴的最高,无法满足时贴的最靠左,输出海报所贴的高度.假设不能贴则输出-1. 一道非常easy,可是我没想出的基础线段树. 算法 ...

  6. 数据传输对象(DTO)介绍及各类型实体比较

    数据传输对象(DTO)介绍及各类型实体比较 本文将介绍DDD分层架构中广泛使用的数据传输对象Dto,并且与领域实体Entity,查询实体QueryObject,视图实体ViewModel等几种实体进行 ...

  7. Socket 学习(三).5 UDP 的弱点

    前面 讲到了,udp 传输文本的例子,发现 udp 确实 比tcp 高效一些,现在我用来传输文件,问题果然来了,结果发现 他不能一次 传输大于 64K的东西! 那么 我自然想到了 切包,多次发送,再合 ...

  8. EasyUi TreeGrid封装

    礼物一:树型实体的抽象与封装 所谓树型实体,就是具有树型结构关系的实体,比如省.市.区.对于初学者,可能会创建三张表进行存储,有经验的开发者通过引入ParentId将设计简化为一张表,但是基于Pare ...

  9. 在Mac OS X 10.8中配置Apache+PHP+MySQL

    在Mac OS X 10.8中配置Apache+PHP+MySQL的内容包括: 配置Apache 配置PHP 安装MySQL 配置PHPAdmin 设置数据库默认字符集 一. 配置Apache 1. ...

  10. nolock引发

      Sql Server之旅——终点站 nolock引发的三级事件的一些思考   曾今有件事情让我记忆犹新,那年刚来携程不久,马上就被安排写一个接口,供企鹅公司调用他们员工的差旅信息,然后我就三下五除 ...