第一章:快速上手
1. 输入下标索引,输出内容
#include <stdio.h>
#include <stdlib.h>
//读取索引序列
void read_column_numbers( int *columns, int len );
//打印字符串
void rearrange( char *intput, int *columns );
int main( void )
{
int columns[ 10 ];
char input[ 100 ];
read_column_numbers( columns, 10 );
while ( NULL != gets( input ) ){
printf("original input:%s\n", input );
rearrange( input, columns );
}
return 0;
}
void read_column_numbers( int *columns, int len )
{
int *end_columns = columns + len;
while ( columns < end_columns - 1 && scanf("%d", columns) && *columns >= 0 ){
columns++;
}
*columns = -1;
}
void rearrange( char *input, int *columns )
{
int begin = 0;
int end = 0;
while ( ( -1 != *columns ) && -1 != *( columns + 1 ) ){
begin = *columns;
end = *( columns + 1 );
if ( begin <= end ){
while ( begin <= end ){
printf("%c", *( input + begin ) );
begin++;
}
}
else{ //逆序显示
while ( end <= begin ){
printf("%c", *( input + begin ) );
begin--;
}
}
columns += 2;
}
}
程序输出:
学会用指针.如果学习C语言不用指针,那编程将会失去乐趣.
第三章:数据
1. 特殊的字符串和字符数组
#include <stdio.h>
#include <string.h>
#define SIZE 12
int main( void )
{
int a = 12;
char *str = "";
char arr[ SIZE ];
int *pa = &a;
int i = 0;
memset( arr, 0, sizeof( char ) * SIZE ); //为变量初始化是一个好习惯
for ( i = 0; i < SIZE - 1; i++ ){
arr[ i ] = 'a' + i;
}
arr[ SIZE - 1 ] = '\0'; //如果想正确输出,还是在末尾加入'\0'
str = "hello world"; //但是字符串就不必了,因为默认会加入'\0'
printf("the int a is:\n");
printf("%d---0x%x\n", *pa, pa );
printf("the string is:\n");
printf("%s---0x%x\n", str, &str );
printf("the char array is:\n");
printf("%s---0x%x\n", arr, &arr );
return 0;
}
程序输出:
2. 有趣的const常量
int const *pci;
这是一个指向整型常量的指针.我们可以修改指针的值,但不能修改它所指向的值,或者换种说法:const修饰的是int,而不是*pci,而int则代表的是其指针所指向的值的类型.如下面代码所示:
#include <stdio.h>
int main( void )
{
int a = 12;
int b = 14;
int const *p = &a;
p = &b; //修改指针的值--right(这里指针的值是地址,而指针所指向的值才是我们通常意义上认为的值)
printf ( "%d\n", *p );
*p = b; //修改指针所指向的值---error
printf("%d\n", *p );
return 0;
}
而:
int * const cpi;
则声明cpi为一个指向整型的常量指针.此时指针是常量,它的值是无法修改的,但是我们可以修改它所指向的整型的值,或者说:const修饰的是int *.
#include <stdio.h>
int main( void )
{
int a = 12;
int b = 14;
int * const p = &a;
p = &b; //修改指针的值--error
printf ( "%d\n", *p );
*p = b; //修改指针所指向的值---right
printf("%d\n", *p );
return 0;
}
但#define也可以创建常量.在C语言中,推荐使用#define,但是在C++中,推荐使用const.用#define还有一个好处是:只要允许使用字面值常量的地方都可以使用#define,比如创建数组.
第五章:操作符和表达式
1. 位操作符
例1:将指定的位设置为1
value = value | 1 << bitNumber;
例2:将指定的位清零
value = value & ~( 1 << bitNumber );
例3:对指定的位进行测试
value & 1 << bitNumber;
2. 左值和右值
左值就是那些能够出现在赋值符号左边的东西.右值就是那些可以出现在赋值符号右边的东西.左值有个要求是:必须表示一个特定的位置(即不能被修改).所以字符串常量不能是左值,因为它在内存中的位置是随机的.
第六章:指针
1. 一些基本的概念
1) 内存和地址
1---内存中的每个位置由独一无二的地址标识
2---内存中的每个位置都包含一个值
2) 值和类型
我们不能通过检查一个值的位来判断它的类型.我们要看这个值如何被解释.
3) NULL指针
NULL作为一个特殊的指针变量,表示不指向任何东西.要使一个指针变量为NULL,给它赋值0即可.
4) 指针常量
假定变量a存储于位置100,则:
*100 = 25;
的意思是把25赋值给a吗?实际上这条语句是非法的,因为字面值100的类型是整型,而间接访问操作符只能作用于指针类型表达式.如果确定要把25存储于位置100,则必须进行强制类型转换:
*( int * ) 100 = 25;
2. 一些实例
1). 在字符串数组中查找单个字符
#include <stdio.h>
//判断value是否在str中,存在返回1,否则返回0
int find_char( char **str, char value );
int main( void )
{
char *str[ 3 ] = { "hello", "world", "python" };
char **strings = str;
if ( find_char( strings, 't' ) ){
printf ( "we find it\n" );
}
else{
printf ( "we do not find it\n" );
}
return 0;
}
int find_char( char **str, char value )
{
while ( NULL != *str++ ){
while ( '\0' != *( *str )++ ){ //注意,这里的++是针对*str,而不是str
if ( value == **str ){
return 1;
}
}
}
return 0;
}
程序输出:
2). 清除数组中的所有元素
正向:
#define N_VALUES 5
float values[N_VALUES];
float *vp = NULL;
for ( vp = &values[0]; vp < &values[N_VALUES]; ){
*vp++ = 0;
}
反向:
#define N_VALUES 5
float values[N_VALUES];
float *vp = NULL;
for ( vp = &values[N_VALUES]; vp > &values[0]; ){
*--vp = 0;
}
错误的反向:
#define N_VALUES 5
float values[N_VALUES];
float *vp = NULL;
for ( vp = &values[N_VALUES - 1]; vp >= &values[0]; vp-- ){
*vp = 0;
}
因为:标准允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针进行比较,但不允许与指向数组第一个元素之前的那个内存位置的指针进行比较.
3) 查找字符串(若追求高效,则用KMP算法)
#include <stdio.h>
//在source中查找chars中的字符
char *find_char( char *source, char *chars );
int main( void )
{
char *source = "abcdef";
char *chars = "xrcqef";
printf ( "%s\n", find_char( source, chars ) );
return 0;
}
char *find_char( char *source, char *chars )
{
char *temp = NULL;
while ( '\0' != *chars ){
temp = source;
while ( '\0' != *source ){
if ( *chars == *source ){
return source;
}
source++;
}
source = temp;
chars++;
}
return NULL;
}
程序输出:
4) 删除字符串中匹配的子字符串
#include <stdio.h>
//删除字符串中等于substr的子字符串
int del_substr( char *str, char *substr );
int main( void )
{
char str[] = "abcdefghijk"; //字符数组可以被修改
char *substr1 = "cde"; //字符串不能被修改
char *substr2 = "ghk";
del_substr( str, substr1 );
printf ( "%s\n", str );
del_substr( str, substr2 );
printf ( "%s\n", str );
return 0;
}
int del_substr( char *str, char *substr )
{
char *delBegin = NULL; //用来保存被删除字符串的起始位置
char *tempSubstr = NULL;
while ( '\0' != *str ){
tempSubstr = substr;
//找到匹配的首字母的位置
while ( '\0' != *str && *substr != *str ){
str++;
}
if ( '\0' == *str ){ //要进行适当的判断,否则可能造成意想不到的BUG
break;
}
delBegin = str;
while ( '\0' != *str && *substr == *str ){
substr++;
str++;
}
if ( '\0' == *substr ){ //匹配成功
while ( *delBegin++ = *str++ ){
;
}
return 1;
}
else{ //继续进行匹配
// substr = '\0';
substr = tempSubstr;
str++;
}
}
return 0;
}
程序输出:
5) 反转字符串
#include <stdio.h>
void reverse( char *str );
int main( void )
{
char str[] = "hello world";
reverse( str );
printf("%s\n", str );
return 0;
}
void reverse( char *str )
{
char *begin = str;
while ( '\0' != *str ){
str++;
}
str--;
while ( begin < str ){
char temp = *begin;
*begin++ = *str;
*str-- = temp;
}
}
程序输出:
6) 寻找素数
#include <stdio.h>
#include <string.h>
#define SIZE 1000
//判断是否为素数
void isPrime( int *arr );
//打印数组
void showArr( int *arr );
int main( void )
{
int arr[ SIZE ];
memset( arr, 0, sizeof( int ) * SIZE );
isPrime( arr );
showArr( arr );
return 0;
}
void isPrime( int *arr )
{
int i = 0;
int j = 0;
for ( ; i < SIZE; i++ ){
*( arr + 1 ) = 0;
if ( ( ( 2 != i ) && !( i % 2 ) ) || ( ( 3 != i ) && !( i % 3 ) ) ){
*( arr + i ) = 0;
}
else{
*( arr + i ) = 1;
}
}
for ( i = 4; i < SIZE; i++ ){
if ( !( arr + i ) ){
continue;
}
for ( j = 2 * i; j < SIZE; j += i ){
arr[ j ] = 0;
}
}
}
void showArr( int *arr )
{
int i = 0;
int count = 0;
for ( ; i < SIZE; i++ ){
if ( *( arr + i ) ){
printf("%3d ", i);
count++;
}
if ( 0 == ( count + 1 ) % 10 ){
printf("\n");
count++;
}
}
printf("\n");
}
程序输出:
第七章:函数
1. 定义分配内存空间,但是声明则只是向编译器说明:嗯,有这样一个东西.
2. 简单理解C语言中函数传递参数是传值而非传址:
#include <stdio.h>
#include <string.h>
void changeArr( int *arr1, int *arr2, int len );
int main( void )
{
int arr1[ 5 ];
int arr2[ 5 ];
int i = 0;
memset( arr1, 0, sizeof( int ) * 5 );
for ( i = 0; i < 5; i++ ){
arr2[ i ] = 1;
}
changeArr( arr1, arr2, 5 );
printf("the arr1 is:\n");
for ( i = 0; i < 5; i++ ){
printf("%d ", arr1[ i ] );
}
printf("\nthe arr2 is:\n");
for ( i = 0; i < 5; i++ ){
printf("%d ", arr2[ i ] );
}
printf("\n");
return 0;
}
void changeArr( int *arr1, int *arr2, int len )
{
int i = 0;
for ( i = 0; i < len; i++ ){
arr1[ i ] = 1000 + i;
}
arr1 = arr2; //为什么这里不起作用呢???
}
程序输出:
因为函数在传递数组的时候,实际上是传值的,这里的值是:数组的地址.所以arr1表示的地址实际上只是一个副本而已,修改副本并不会影响原先的值.
3) 简单的递归程序
1---输入数字,输出字符.如输入4267,输出'4','2','6','7'.
#include <stdio.h>
void binary_to_ascii( unsigned int value );
int main( void )
{
binary_to_ascii( 4267 );
printf("\n");
return 0;
}
void binary_to_ascii( unsigned int value )
{
unsigned int quotient;
quotient = value / 10;
if ( 0 != quotient ){
binary_to_ascii( quotient );
}
printf ( "%c", value % 10 + '0' );
}
2---递归与迭代版本之计算菲波那契数
#include <stdio.h>
//递归版本
long fibonacci1( int n );
//迭代版本
long fibonacci2( int n );
int main( void )
{
printf("%d\n", fibonacci1( 20 ) );
printf("%d\n", fibonacci2( 20 ) );
return 0;
}
long fibonacci1( int n )
{
if ( n <= 2 ){
return 1;
}
return fibonacci1( n - 1 ) + fibonacci1( n - 2 );
}
long fibonacci2( int n )
{
int result = 1;
int n_1 = 1;
int n_2 = 1;
while ( n > 2 ){
n -= 1;
n_1 = n_2;
n_2 = result;
result = n_1 + n_2;
}
return result;
}
程序输出:
如果迭代够清晰,则用迭代,从效率方面考虑.
4) 可变参数列表
#include <stdio.h>
#include <stdarg.h>
float average( int n_values, ...);
int main( void )
{
float aveValue = 0.0;
aveValue = average( 5, 1, 2, 3, 4, 5, 6 ); //这里第一个参数代表传递参数的个数,所以6在函数中并未被计算
printf("%f\n", aveValue );
return 0;
}
float average( int n_values, ...)
{
va_list var_arg;
int count;
float sum = 0;
va_start( var_arg, n_values );
for ( count = 0; count < n_values; count++ ){
sum += va_arg( var_arg, int );
}
va_end( var_arg );
return sum / n_values;
}
程序输出:
1. 类型va_list配合va_start, va_arg, va_end使用
2. va_start初始化包含两个参数:第一个参数是va_list变量的名字。第二个参数是省略号前最后一个有名字的参数。va_start的初始化过程把var_arg变量设置为指向可变参数部分的第一个参数。
3. 为了访问参数,需要使用va_arg,这个宏接受两个参数:va_list变量和参数列表中下一个参数的类型。
4. 最后,当访问完毕最后一个可变参数之后,我们需要调用va_end。
5) 公约数的递归求解
#include <stdio.h>
int gcd( int m, int n )
{
if ( 0 == n || 0 == m % n ){
return n;
}
else{
return gcd( n, m % n );
}
}
int main(void)
{
int m = 23456;
int n = 3456;
printf("%d\n", gcd( m, n ));
return 0;
}
程序输出: