C基础(学习笔记持续更新中)

第一章:变量和常亮

1. 变量名

注意不要取名为 begin 、 end 、 next 、 index、 list、 link 等

2. 基本数据类型

int和long区别如下:
32位系统:long是4字节32位,int是4字节32位。
64位系统:long是8字节64位,int是4字节32位。
在这里插入图片描述注:
1. float的精度是保证至少7位有效数字是准确的
2. C++代码中将数字赋值给float变量,如果给出警告warning C4305: “=”: 从“int”到“float”截断,则超出了float的精度范围,在我的测试中赋值为16777216及以下整数没有警告,赋值为16777217时给出了警告。

3. 数据类型转换

整型数据也可以转换为字符型数据,但只会留下最右边的一个字节(第0位 到第7位),其他字节丢弃

  char a= 334; 
  cout << a<<",";//直留下最右边的一个字节,即334-256=78,或者说只留下16进制表示的后两位所表示的数值 
  int b = a;
  cout << b;
	输出结果:N,78

4. 转义字符

\n, \\, \0, \’, \"。

第二章

1. 输入输出控制符

用scanf可以一次读入多个类型不同的变量,只要输入的各项之间用空格分隔即可。

  • 输入字符时,不会跳过空格(空格也会被当作字符读入), 输入其他类型的数据时,会跳过空格
  • 如果在输入中有scanf中出现的非控制字符,则这些字符会被跳过,但要在输入框里对应非控制字符。
int n,m; char c; float f;
scanf("%d %c,%f:%d",&n,&c,&f,&m);
printf("%d,%c,%f,%d",n,c,f,m);
return 0;

输入:12 k,3.75:290【Enter】
输出:12,k,3.750000,290

2. 控制printf 输出整数的宽度

%nd(如%4d,%12d) 以n字符宽度输出整数, 宽度不足时用空格填充。
%0nd(如%04d,%012d) 以n字符宽度输出整数, 宽度不足时用0填充。
%.nf(如%.4f,%.3f) 输出浮点数, 精确到小数点后n位

3. printf 中的格式控制符

%x: 以十六进制形式读入或输出 int整数

  • 0x… 表示输入形式代表输入的是16进制数整形,用以区别于十进制
printf("%x,%d,%u",0x17, 0xffffffff,0xffffffff);
输出 => 11,-1,4294967295
  • %u: 以无符号整数形式输出整数

举例:

int n,m; 
scanf("%d %u",&n,&m ); //读取之后m内保存的还是-1,但是输出的时候自动以无符号数的形式输出 
printf("%d  %u",n,m);
输入:1 -1
输出:1  4294967295(-1的补码,以无符号数的形式输出)
int n,m; 
scanf("%d %d",&n,&m );
printf("%d  %u",n,m);
输入:1 -1
输出:1  4294967295(-1的补码,以无符号数的形式输出)

4. 表达式的精度

a+b、 a-b、 a*b、 a/b这四个表达式的值,就是a和b做算术运算的结果。表达式的值的类型,以操作数中精度高的类型为准。
精度:double > long long > int > short > char

5. 溢出

两个整数类型进行加、减、乘都可能导致计算结果超出了结果类型所能表示的范围,这种情况就叫做溢出。溢出部分直接被丢弃。

#include<iostream> 
using namespace std;
int main() {  
    unsigned int a ;
    cin >> a;        //此处输入4294967298
    cout << a<<endl;         //输出为4294967295
    
    unsigned int b = 4294967298; 
    cout << b;        //输出为2
    return 0;
}

6. 自增自减运算

++a ,–a 则返回值为加减之后的值,a++ ,a–在后则返回值为操作前的值,但是操作结束之后a的值总是有相应的变化。

7. 逻辑表达式

逻辑表达式是短路计算的,即对逻辑表达式的计算,在整个表
达式的值已经能够断定的时候即会停止

  • exp1 && exp2 : 如果已经算出表达式exp1为假,那么整个表达式的值肯定为假,于是表达式exp2就不需要再计算
  • exp1 || exp2 : 如果已经算出exp1为真,那么整个表达式必定为真,于是exp2也不必计算
  • 所以在写代码的时候,判定里面最好不要有自增自减运算,因为不能确定所有逻辑表达式是否能被执行****
    int a = 0, b = 2;
	int n = a++ && ++b;
	cout << n << "," << a << "," << b;
输出结果为:0,1,2

8. scanf 和cin 输入数据

  • cin >> m >> n … 表达式的值,在成功读入所有变量时为true,否则为false
   int n,m;
   freopen("c:\\Users\\Administrator\\Downloads\\...txt", "r", stdin);
   while(cin >> n >> m ) {
   printf("%d",n+m);
}

注: Ctrl+Z 然后 回车,程序结束

  • 用cin读入所有输入的字符, 包括空格,回车:
   #include <iostream>
using namespace std;
int main()
{
	freopen("c:\\Users\\Administrator\\Downloads\\11.txt", "r", stdin);
	int c;
	while ((c = cin.get()) != EOF) {
	%while( cin >> n)    //读取的过程中,cin.get()的返回值一直是int类型的非负数,直到读取结束时,返回-1,即EOF%
		cout << (char)c;
	}
	return 0;
} 
  • scanf()表达式的值:
    scanf(…)表达式的值为int,表示成功读入的变量个数。
   int n,m;
   freopen("c:\\Users\\Administrator\\Downloads\\...txt", "r", stdin);
   printf("%d",scanf("%d%d",&n,&m));  
  • 用scanf读入所有输入的字符,包括空格,回车
   char c;
   freopen("c:\\Users\\Administrator\\Downloads\\...txt", "r", stdin);
   while(scanf("%c",&c) != EOF) {
   // 或 while(scanf("%d",&n) == 1) %
   //每次读取了几项,scanf的返回值就是多少,此处每次都读取一项,故scanf()=1,直到数据读取结束时,scanf()返回-1  
   printf("%c",c);
}
   int n,m;
   freopen("c:\\Users\\Administrator\\Downloads\\...txt", "r", stdin);
   while(scanf("%d%d",&n,&m) == 2) {
   printf("%d",n+m);
}

注:直到输入 Ctrl+Z 然后 回车,程序结束
注:读取文件数据时,读取结束的时候,cin或者scanf()会自动返回EOF,即cin>>n 为false,scanf(" ",& ,&…)==EOF, (c = cin.get()) == EOF

  • freopen对于数据的读写
freopen("c:\\Users\\Administrator\\Downloads\\1.txt", "r", stdin);
freopen("c:\\Users\\Administrator\\Downloads\\out1.txt", "w", stdout); 

第三章:条件语句和循环语句

1. switch语句

表达式的值 必须是整数类型(int,char ……)

switch (表达式) { //表达式的值 必须是整数类型(int,char ……)
		case 常量表达式1: //常量表达式必须是整数类型的常量(int,char…)
			语句组1
			break;
			case 常量表达式2:
				语句组2
				break;
				……
			case 常量表达式n:
				语句组n
					break;
			default:
				语句组n + 1
  • switch语句在进入某case分支后,会一直执行到第一个碰到的“break;”,即使这个“break;”是在后面的case分支里面。如果没有碰到“break;”,则会向下一直执行到switch语句末尾的“}”,包括“default:”部分的语句组也会被执行。

  • 比如下面的程序n%6=1,则执行case 1:,因为在case 1里不存在break;,所以会一直向后执行,直至遇到break;

 #include <iostream>
using namespace std;
int main() {
	int n;
	scanf("%d", &n);
	switch (n % 6) {
	case 0:
		printf("case 0");
		break;
	case 1:
		printf("case 1");
	case 2:
	case 3:
		printf("case 2 or 3");
		break;
	case 4:
		printf("case 4");
		break;
	}
	return 0;
}

2. for循环

for循环结构里的“表达式1”和“表达式3”都可以是用逗号连接的若干个表达式

for( int i= 15, j = 0; i > j; i - =2 ,j += 3)
     cout << i << "," << j << endl;

3. break,continue

break跳出该层循环

  • continue立即结束该次循环,并调到循环判定位置,判定下次循环是否要进行

4. 找质数的循环算法

#include <iostream>
using namespace std;
int main() {
	int n;
	cin >> n;
	cout << 2 << endl;
	for (int i = 3; i <= n; i += 2) { //每次判断i是否是质数
		int k;
		for (k = 3; k < i; k += 2) {
			if (i % k == 0)
				break;
			if (k*k > i)
				break;
		}
		if (k*k > i) cout << i << endl; // k大于i的平方根后就不必再试  
	}
	return 0;
}
}

第四章:数组

1. 筛法求素数

#include <iostream> //筛法求素数
#include <cmath>
using namespace std;
#define MAX_NUM 10000000
bool isPrime[MAX_NUM + 10]; //最终如果isPrime[i]为1,则表示i是素数
int main()
{
	for (int i = 2; i <= MAX_NUM; ++i) //开始假设所有数都是素数
		isPrime[i] = true;
	for (int i = 2; i <= MAX_NUM; ++i) { //每次将一个素数的所有倍数标记为非素数
		if (isPrime[i]) //只标记素数的倍数
			for (int j = 2 * i; j <= MAX_NUM; j += i)
				isPrime[j] = false; //将素数 i 的倍数标记为非素数
	}
	for (int i = 2; i <= MAX_NUM; ++i)
		if (isPrime[i])
			cout << i << endl;
	return 0;
}

2. 矩阵的乘法(练习)

#include <iostream>
#include <cstdio>
using namespace std;
int a[2][3], b[3][3], c[2][3];

int main()
{
	int m, n, p, q;
	cin >> m >> n >> p >> q;
	for (int i = 0; i < m; ++i)
		for (int j = 0; j < n; ++j)
			scanf("%d", &a[i][j]);

	for (int i = 0; i < p; ++i)
		for (int j = 0; j < q; ++j)
			scanf("%d", &b[i][j]);

	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < q; ++j) {
			for (int k = 0; k < n; ++k) {
				c[i][j] += a[i][k] * b[k][j];
			}
		}
	} 
	for (int i = 0; i < m; ++i) {
		for (int j = 0; j < q; ++j) {
			printf("%2d ", c[i][j]);
		}
		printf("\n");
	} 
	return 0;
}

第五章:函数

1. 形参and实参

  • 一般情况下,形参只是是实参的一个复制,形参的改变不会改变实参;除非形参的类型是数组,指针引用
  • 数组作为形参:
    数组作为函数参数时,是传引用的,即形参数组改变了,实参数组也会改变。
    一位数组作形参时,格式为:类型名 数组名,比如 int a[ ],此处仅仅表示数组的初始地址。
    二维数组作形参时,格式为a[ ][N],此处必须写明列数的原因是,有了列数,才能通过下标计算出每个元素的地址。
    数组a[i][j]的地址 = a[0][0]地址 + i*N *sizeof(a[i][j])+ j * sizeof(a[i][j]),此处N为数组的列数。

2. 库函数和头文件

  • 数学函数 cmath:
    int abs(int x) 求整型数x的绝对值
    double cos(double x) 求x(弧度)的余弦
    double fabs(double x) 求浮点数x的绝对值
    int ceil(double x) 求不小于x的最小整数
    double sin(double x) 求x(弧度)的正弦
    double sqrt(double x) 求x的平方根

  • 字符处理函数 ctype:
    int isdigit(int c) 判断 c 是否是数字字符
    int isalpha(int c) 判断 c 是否是一个字母
    int isalnum(int c) 判断 c 是否是一个数字或字母
    int islower(int c) 判断 c 是否是一个小写字母
    int islower(int c) 判断 c 是否是一个小写字母
    int isupper(int c) 判断 c 是否是一个大写字母
    int toupper(int c) 如果 c 是一个小写字母,则返回对应大写字母
    int tolower (int c) 如果 c 是一个大写字母,则返回对应小写字母

3. 递归函数

递归函数一定要注意,有终止条件,否则就会无穷递归使得程序无法终止而崩溃
求斐波那契数列第 n 项:

int Fib(int n)
	{
		if (n == 1 || n == 2)
			return 1;
		else
			return Fib(n - 1) + Fib(n - 2);
	}

4. 位运算

  • C/C++语言提供了六种位运算符来进行位运算操作:

    & 按位与(双目)
    | 按位或(双目)
    ^ 按位异或(双目)
    ~ 按位非(取反)(单目)
    << 左移(双目)
    '>> 右移(双目)

  • 异或运算:
    特点:如果 a^b=c,那么就有 c^b = a以及c^a=b。 (穷举法可证)
    异或运算还能实现不通过临时变量,就能交换两个变量的值:
    int a = 5, b = 7;
    a = a ^ b;
    b = b ^ a;
    a = a ^ b;

  • 左移和右移:
    左移1位,就等于是乘以2, 左移n位,就等于是乘以2n。而左移操作比乘法操作快得多。
    右移n位,就相当于左操作数除以2n,并且将结果往小里取整

    比如: -25 >> 4 = -2, -2 >> 4 = -1,18 >> 4 = 1

大多数C/C++编译器规定, 如果原符号位为1,则右移时高位就补充1,原符号位为0,则右移时高位就补充0。如果操作数为unsigned类型数的时候,没有符号位,所以右移操作时高位就补充0。

举例:
    #include <stdio.h>
    int main()
    {
	    int n1 = 15;
	    short n2 = -15;
	    unsigned short n3 = 0xffe0;
	    char c = 15;
	    n1 = n1 >> 2;
	    n2 >>= 3;
	    n3 >>= 4;
	    c >>= 3;
	    printf("n1=%d,n2=%x,n3=%x,c=%x", n1, n2, n3, c);
    } //输出结果是: n1=3,n2=fffffffe,n3=ffe,c=1
    // **n2由于输出时的形式是%x被自动转换成int输出** 

总结: 取反用某位异或^1,替换用某位置零后再去或|,求某位的值用1&它。

第六章:字符串

1. 字符串的表达形式

“string”形式

  • 放在字符数组中,以\0结尾,注意,字符串数组一定是包含\0的一维数组 !
  • string字符串对象

2.字符串读取

  • 可以直接给字符串数组赋值。比如char a[]=“abc” 或 char a[4]=“abc”
  • scanf()和cin读取字符串数组:
  • 字符数组同样可以用cout、 printf输出,用cin、 scanf读入。用cin、scanf将字符串读入字符数组时,会自动在字符数组中字符串的末尾加上
    ‘\0。
    char line[100];
    scanf("%s",line);//遇到空格就输入\0,之后的输入都不会被输出
    //cin>>line;也是一样
    printf("%s",line);
  • cin.getline(char buf[ ], int bufsize);
    读取一整行字符串的方法( 可以包含空格,制表符等):
    注意:读入字符数必须要小于bufsize-1,否则就会导致数组越界,读入最后自动添加\0。
freopen("c:\\Users\\Administrator\\Downloads\\11.txt", "r", 
char line[100];
cin.getline(line,sizeof(line));
cout<<line;
  • gets(char buf[])
  • 读入一整行,自动添加\0在末尾,但是有可能会数组越界。键盘输入^Z [Enter]则输入结束。
#include <iostream>  
using namespace std;
int main() {
	freopen("c:\\Users\\Administrator\\Downloads\\11.txt", "r", stdin);
	char line[100];
	while (gets(line))
	{
		cout << line;
	}
	return 0;
}

3. 字符串库函数

  • 要声明 #include
  • 字符串函数都根据’\0’来判断字符串结尾
  • 形参为char [ ]类型,则实参可以是char数组字符串常量
    - 函数名称 功能
    strcat 将一个字符串连接到另一个字符串后面
    strchr 查找某字符在字符串中最先出现的位置
    strrchr 查找某字符在字符串中最后出现的位置
    strstr 求子串的位置
    strcmp 比较两个字符串的大小(大小写相关)
    stricmp 比较两个字符串的大小(大小写无关)
    strcpy 字符串拷贝
    strlen 求字符串长度
    strlwr 将字符串变成小写
    strupr 将字符串变成大写
    strncat 将一个字符串的前n个字符连接到另一个字符串后面
    strncmp 比较两个字符串的前n个字符
    strncpy 拷贝字符串的前n个字符
    strtok 抽取被指定字符分隔的子串
    atoi 将字符串转换为整数(在cstdlib中声明)
    atof 将字符串转换为实数(在cstdlib中声明)
    itoa 将整数转换为字符串(在cstdlib中声明)

使用函数的时候要注意,数组作为形参时,实参随形参的变换而改变。

4. 字符串数组 / 字符串函数的循环判定

  • 在判定条件中最好不要加入字符串函数,否则每一次的循环判定都会执行一次字符串函数,这是效率上的极大浪费。
  • 比如:
char s[100]="test";
for( int i = 0; i < strlen(s) ; ++ i ) {
    s[i] = s[i]+1;
}
  • 正确的判定方法:
char s[100] = "test";
//取出s的长度存放在一个变量里面,然后在循环的时候使用该变量
	for (int i = 0; i < strlen(s); ++i) {
		s[i] = s[i] + 1;
	}

	char s[100] = "test";
//直接判定字符串元素是否读到\0,读到则判定循环结束 (推荐!!)
	for (int i = 0; s[i]; ++i) {
		s[i] = s[i] + 1;
	}

5. 应用习题

判定字符串字串出现的位置:

#include <iostream> 
#include <cstring>
using namespace std;

void Strstr(char dest[], char src[]) { 
	if (src[0] == 0) {                   //空串是所有字符串的子串
		cout << " Null-substring!";
		return;
	}
	int  i;
	for ( i = 0; dest[i];++i) {
		int j=0 ;
		if (dest[i] == src[j]) {         //比对的起始判定
			for ( ; src[j]; ++j) {
				if (dest[i + j] != src[ j])   //对比不同则跳出循环
					break;
			}
		} 
		if (src[j] == 0) {
			cout << i; 
			return;
		} 
	}
	if (dest[i] == 0)
		cout << "not a substring!"; 
	return;
} 
int main() {
	char a[100], b[100];
	cin.getline(a, sizeof(a));
	cin.getline(b, sizeof(b));
	Strstr(a, b); 
	return 0;
}

第七章:指针(重点!)

1. 概念

每个变量都被存储在从某个内存地址开始的若干个字节中,指针变量则代表一个内存地址(一般是4个字节,如果是64bit的编译器,也可以是8个字节)。

  • 先定义变量,才能使用指针,如果直接用指针对某个地址进行赋值操作,这时我们不能确定这个地址能否被允许访问(地址已经被其他变量占用,或者被程序指令内存占用),这样会导致程序报错。
  • & x 表示变量x的地址,即指向x的指针,其数据的类型是T*。

2. 指针的意义/作用

  • 有了指针,程序对内存的访问区域也不仅局限于变量所占据的内存区域,极大地扩展了程序的访问区域。
  • 比如:通过对指针进行加减操作,可以灵活地访问变量前后的内存区域!这些内存区域,未必有变量与之对应,所以仅仅通过变量无法访问。

3. 指针的相互赋值:

  • 不同类型的指针,如果没有经过强制类型转换,不可以直接相互赋值!!!
  • 对指针进行运算固然可以灵活地访问内存,但一定要考虑被访问的内存地址是否被操作系统允许访问!!!

举例:

int * pn, char * pc, char c = 0x65;
pn = pc; //类型不匹配,编译出错
pn = & c; //类型不匹配,编译出错
pn = (int * ) & c; //强制类型转换
int n = * pn; //由于pn指针类型经过转换,原本是char*类型,现在是int*类型,多出的3个字节不确定,所以赋值后的n值也不确定
* pn = 0x12345678; //编译能过但运行可能出错
//原因是,原本pn指针是char*类型,后来虽强制变为int*类型,但是*pn所表示变量还是1个字节(char类型),后面3个字节始终没有占用。此时对*pn赋值int类型的数据,多出的3个字节很有可能会占据其他变量或者指令的内存,从而导致程序出错。

3. 指针的运算

  • 相同类型的指针可以比大小

  • 两个同类型的指针变量,可以相减:p1 – p2 = ( 地址p1 – 地址 p2 ) / sizeof(T)

  • 指针变量加减一个整数的结果是指针:
    比如 p+n,指向地址:地址p + n × sizeof(T)
    同样,n+p, p-n , *(p+n), *(p-n)

  • 指针变量可以自增、自减(同上)

  • 指针和数组之间的关系: p[n] 等价于 * (p+n)

4. 空指针

  • 一些字符串操作库函数,比如char * strstr(const char * str, char * subStr)如果操作不成功,就会返回Null。
  • 对于字符串的处理,也会有空指针的使用。比如char * strtok(char * str, const char * delim)函数,后续调用,第一个参数都必须是NULL。

1.逻辑上表示不指向任何内存,一般可用于超越现有值域的表示;

2.假如你的函数返回值被误解已经计算出一个合法值,如果你返回的是一个指针,她除了指向有效值外还可以指向空指针

3.作为一个过度的容器,两个要换位置总还要一个用来临时存放

4.定义一个指针,如果先不用就要把它赋给NULL,不然有可能会随意指向内存造成程序崩溃

原文链接:https://blog.csdn.net/Moon_K_H/article/details/42467421

5. 指针作为参数

  • 作为函数形参时, T *p 和 T p[ ] 等价。
  • 指针作为参数时,实参之所以会被改变,原因不是指针变量被改变,而是实参变量通过形参拷贝的指针 *pointer 被改变。

6. 指针和数组

  • 数组的名字是一个常量指针,指向数组的起始地址。

    ------对于数组 T a[N],a的类型是T*,代表数组 a[N] 的起始地址(与 &a [0]等价 )。a是一个常量地址,可以给一个相同类型的指针变量 T*p赋值,但不能对被赋值。

  • 二维数组和指针:

  1. 对于T a[M][N],a[i] 是一个一维数组,a[i] 的类型也是T *。
  2. a指向数组的起始地址,a[i] 指向第i行的初始地址,两个指针的类型都是T *。
  3. a 和a[i] 的字节数不同,sizeof(a)= sizeof(T) * M * N,代表二维数组所有元素的字节数。 sizeof(a[i])=sizeof(T) * N,代表第i行数组所有元素的字节数;

举例:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
int main()
{
	//一维数组
	int b[3] = { 1,2,3 };
	cout << sizeof(b) << "," << *b << "," << sizeof(b[1]) << endl;

	//二维数组
	int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	cout << a[1]<<","<<*a[1]<< "," << sizeof(a[1]) << endl;
	cout << a<< "," << sizeof(a) << "," << sizeof(a[1][2]) << endl; 
	return 0;
}
                输出:
                12,1,4
                001CF86C,5,16
                001CF85C,48,4

7. 指向指针的指针(略)

8. 指针和字符串

  • 字符串常量的类型是 const char *,字符数组 a[N]名的类型是 char *。
    注意:字符串变量还是保存在数组中,只不过可以用指针 char * a 获取字符串数组的起始地址,即char *a=k(char k[N]), 方便对字符串进行操作。
  1. 读取字符串:
    定义char * a:
    • cin>>a 和 scanf("%s",a) 可以读取字符串直到空格,且没长度限制。
    • cin.getline(char a[ ] / char *a, n) 和 gets(char a[ ] / char *a) 也可以读取一行字符串,包括空格,但前者有长度限制。
  2. 输出字符串,cout<<a 或者 printf(a),输出字符串直到\0。
  • 字符串操作库函数
字符串操作库函数
函数名称 功能
strcat 将一个字符串连接到另一个字符串后面
strchr 查找某字符在字符串中最先出现的位置
strrchr 查找某字符在字符串中最后出现的位置
strstr 求子串的位置
strcmp 比较两个字符串的大小(大小写相关)
stricmp 比较两个字符串的大小(大小写无关)
strcpy 字符串拷贝
strlen 求字符串长度
strlwr 将字符串变成小写
strupr 将字符串变成大写
strncat 将一个字符串的前n个字符连接到另一个字符串后面
strncmp 比较两个字符串的前n个字符
strncmp 比较两个字符串的前n个字符
strncpy 拷贝字符串的前n个字符
strtok 抽取被指定字符分隔的子串
atoi 将字符串转换为整数(在cstdlib中声明)
atof 将字符串转换为实数(在cstdlib中声明)
itoa 将整数转换为字符串(在cstdlib中声明)

注释:每个字符串函数的返回值都不固定,其中一些返回值是指针,有些会是空指针Null,代表函数未执行成功。

9. void指针

  • void指针:
    void * p 未定义数据类型,所以sizeof§也没有定义,所以++p,–p, p+n, p-n, p+=n, p-=n 也没有定义.
    注意:void *()函数一定要有返回值,而void ()函数可以没有!!

10. 内存库操作函数

1)初始化函数 void * memset ( )

形式:void *memset(void *s, int ch, size_t n);

memset() 函数常用于内存空间初始化。
本质: 用来对一段内存空间全部设置为某个字符,一般用在对定义的字符串进行初始化。
方式: 将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s,ch只有最低的字节起作用。
优点:
1. 对较大的结构体或数组进行清零操作的一种最快方法 。
2. memset可以方便的清空一个结构类型的变量或数组。

#include <iostream>
#include <cstring>
using namespace std;
int main()
{
    char a[5];
    memset(a,'1',5;
    for(int i=0;i<5;i++)
        cout<<a[i]<<"";
    system("pause");
    return 0;
}

而,如下程序想把数组中的元素值设置成1,却是不可行的

#include <iostream>
#include <cstring>
#include <windows.h>
using namespace std;
int main()
{
    int a[5];
    memset(a,1,20; //也等价于memset(a,1,sizeof(a));.
    for(int i=0;i<5;i++)
        cout<<a[i]<<"";
    system("pause");
    return 0;
}

问题是:第一个程序为什么可以,而第二个不行?

答:因为第一个程序的数组a是字符型的,字符型占据内存大小是1Byte,而memset函数也是以字节为单位进行赋值的,所以你输出没有问题。
而第二个程序a是整型的,使用 memset还是按字节赋值,这样赋值完以后,每个数组元素的值实际上是0x01010101即十进制的16843009。
如果用memset(a,1,20),就是对a指向的内n存的20个字节进行赋值,每个都用数1去填充,转为二进制后,1就是00000001,占一个字节。一个int元素是4字节,合一起是0000 0001,0000 0001,0000 0001,0000 0001,转化成十六进制就是0x01010101,就等于16843009,就完成了对一个int元素的赋值了。

  • 所以用memset对非字符型数组赋初值,除了令其等于1或0,其他是不可取的 !!!

另例:

#include<iostream>
#include<cstring>
using namespace std;
int main()
{
	const char *s = "GoldenGlobalView";
	memset(s, 'G', 6);                
	/*这里没有问题,可以编译运行,单步运行到这里会提示内存访问冲突,
	肯定会访问冲突,s指向的是不可写空间。*/
	printf("%s", s);
	getchar();
	return 0;
}

以上例子出现内存访问冲突应该是因为s被当做常量放入程序存储空间,如果修改为 char s[]="Golden Global View";则没有问题了。

  • 对于结构体
    有一个结构体Some x,可以这样清零: memset(&x,0,sizeof(Some));
    如果是一个结构体的数组Some x[10],可以这样:memset(x,0,sizeof(Some)*10);

2)内存拷贝函数 void * memcpy ( )

形式:void * memcpy(void * dest, void * src, int n);

  • source和destin所指的内存区域可能重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove() 可以用来处理重叠区域。函数返回指向des的指针。
  • memcpy() 的实现方法:
    要考虑到,目标的起始地址在源空间内,出现内存覆盖的情况
//按照字节(Byte)拷贝实现的my_memcpy 
void* my_memcpy(void* dst,const void* src,int n)
{
    if (dst == NULL || src == NULL || n <= 0) {
       return NULL;//void* 一定要有返回值 void可以没有返回值  
    } 
    char* pdst = (char *)dst;
    char* psrc = (char *)src; 
    if (psrc < pdst && pdst < psrc + n) {   //关键!如果出现内存覆盖的情况,就要从后向前copy
        pdst = pdst + n - 1;
        psrc = psrc + n - 1;
        while (n--) {
            *pdst = *psrc;
            pdst--;  psrc--;
        }
    }
    else {
        while (n--) {
            *pdst = *psrc;
            pdst++; psrc++;
        } 
   }
    return dst;
}
  • 注意:source和destin都不一定是数组,任意的可读写的空间均可。
  • strcpy 和memcpy 的区别:
    1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
    2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。

11. 函数指针

程序运行期间,每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址(也称“入口地址” )。

我们可以将函数的入口地址赋给一个指针变量 ,使该指针变量指向该函数。然后通过指针变量就可以调用这个函数。这种指向函数的指针变量称为“函数指针”。

  • 定义形式
    类型名 (* 指针变量名)(参数类型1, 参数类型2,…);
    例如: int (*pf)(int ,char);

  • 使用方法
    用一个原型匹配的函数的名字给一个函数指针赋值。
    通过函数指针可以调用它所指向的函数,写法为: 函数指针名(实参表);

  • 举例 1:

#include<iostream>
#include<cstring>
using namespace std;
void PrintMin(int a, int b) {
	if (a < b)
		printf("%d", a);
	else
		printf("%d", b);
}
int main() {
	void(*pf)(int, int);
	int x = 4, y = 5;
	pf = PrintMin;
	pf(x, y);
	return 0;
}
  • 举例 2:
    类似 qsort库函数: void qsort (void *base, int nelem, unsigned int width,int ( * pfCompare)( const void *, const void *));

在调用函数时,用实参给调用的函数的形参中定义的函数指针赋值,之后在函数中直接用函数指针分别调用不同的判定标准函数,实现程序的简洁化。

#include <iostream>
using namespace std;
void * MyMax(void *a, int width, int n, int(*pfCompare)(void * n1, void * n2)) { 
	char* result = (char*)a;
	for (int i = 1; i < n; ++i) {
		if (pfCompare(result, (char*)a + i * width) < 0)
			result = (char*)a + i * width;
	}
	return result;
}
int Compare1(void * n1,void * n2)
{
	int * p1 = (int * )n1;
	int * p2 = (int * )n2;
	return ((*p1)%10) - ((*p2)%10);
}
int Compare2(void * n1,void * n2)
{
	int * p1 = (int * )n1;
	int * p2 = (int * )n2;
	return *p1 - *p2;
}
#define eps 1e-6
int	Compare3(void * n1,void * n2)
{
	float * p1 = (float * )n1;
	float * p2 = (float * )n2;
	if( * p1 - * p2 > eps)
		return 1;
	else if(* p2 - * p1 > eps)
		return -1;
	else
		return 0; 
}

int main()
{
	int t;
	int a[10];
	float d[10];
	cin >> t;
	while(t--) {
		int n;
		cin >> n;
		for(int i = 0;i < n; ++i)
			cin >> a[i];
		for(int i = 0;i < n; ++i)
			cin >> d[i];
		int * p = (int *) MyMax(a,sizeof(int),n,Compare1);
		cout << * p << endl;
		p = (int *) MyMax(a,sizeof(int),n,Compare2);
		cout << * p << endl;
		float * pd = (float * )MyMax(d,sizeof(float),n,Compare3);
		cout << * pd << endl;
	}
	return 0;
}

第八章:结构(struct)和变量

1. 结构(struct) … 未完

  • 在现实问题中,常常需要用一组不同类型的数据来描述一个事物。如果编程时要用多个不同类型的变量来描述一个事物,就很麻烦。所以C++允许程序员自己定义一个包含不同数据类型的新的数据类型。
 struct 结构名
{
	类型名 成员变量名;
		类型名 成员变量名;
		类型名 成员变量名;
		……
}
  • 结构变量所占的内存空间的大小,就是结构中所有成员变量大小之和
  • 一个结构的成员变量可以是任何类型的,包括可以是另一个结构类型,层层套用
struct Date {
	int year;
	int month;
	int day;
};
struct StudentEx {
	unsigned ID;
	char szName[20];
	float fGPA;
	Date birthday;
};
  • 成员变量的访问:结构变量名.成员变量名

  • 重点:结构数组和指针:

    1. 结构也可以定义为结构数组的形式,如 StudentEx MyClass [50];
    2. 指向结构变量的指针:
      通过指针,可以访问其指向的结构变量的成员变量:
      通过指针,访问其指向的结构变量的成员变量:
      - 形式:指针->成员变量名 或: (* 指针).成员变量名
#include<iostream>
#include<cstring>
using namespace std;
struct StudentEx {
	unsigned ID;
	char szName[20];
	double fGPA;
};
int main() {
	StudentEx Stu;
	StudentEx * pStu;
	pStu = &Stu;
	pStu->ID = 12345;
	(*pStu).fGPA = 3.48;
	cout << Stu.ID << endl; //输出 12345
	cout << Stu.fGPA << endl; //输出 3.48 
	return 0;
}
 

2. 全局 / 局部变量

  • 非 / 静态变量:
  1. 全局变量都是静态变量。
  2. 静态变量的存放地址,在整个程序运行期间,都是固定不变的
  3. 静态变量在程序中只初始化一次
  4. 局部变量定义时如果前面加了“static”关键字,则该变量也成为静态变量,即静态局部变量
#include <iostream>
using namespace std;
void Func() {
	static int n = 4; //静态变量只初始化一次
	cout << n <<" ";
	++n;
}
int main() {
	Func(); Func(); Func();
} 
输出结果:
4 5 6
  1. 如果未明确初始化,则静态变量会被自动初始化成全0(每个bit都是0),局部非静态变量的值则随机。
  • 全局变量:
  1. 所有函数的外面的变量叫全局变量。
  2. 如果未明确初始化,则全局变量会被自动初始化成全0(每个bit都是0)。
  • 局部变量:
  1. 定义在函数内部的变量。
  2. 全局变量在所有函数中均可以使用,局部变量只能在定义它的函数内部使用
  3. 非静态变量一定是局部变量,而局部变量不一定是非静态变量(比如前面加一个static)。
  4. 非静态变量(局部变量)的地址每次函数调用时都可能不同,在函数的一次执行期间不变。
  5. 如果未明确初始化,则静态变量会被自动初始化成全0(每个bit都是0),局部非静态变量的值则随机。

3. 标识符的作用域

  • 变量名、函数名、类型名统称为“标识符”。一个标识符能够起作用的范围,叫做该标识符的作用域
  • 在单文件的程序中,结构、 函数全局变量的作用域是其定义所在的整个文件。

4. 变量的生存期

  • 所谓变量的“生存期”,指的是在此期间,变量占有的内存空间只能归它使用,不会被用来存放别的东西。
  • 变量的生存期终止,就意味着该变量不再占有内存空间,它原来占有的内存空间,随时可能被派做他用。
  • 全局变量的生存期,从程序被装入内存开始,到整个程序结束。
  • 静态局部变量static的生存期,从定义它语句第一次被执行开始,到整个程序结束为止。
  • 函数形参的生存期从函数执行开始,到函数返回时结束。
  • 非静态局部变量的生存期,从执行到定义它的语句开始,一旦程序执行到了它的作用域外,其生存期即告终止。

作业:在静态局部变量里面写出strtok()函数的实现过程,用于提取一句话中的每个单词和数字!!!

发布了10 篇原创文章 · 获赞 0 · 访问量 360

猜你喜欢

转载自blog.csdn.net/Cabbage_W/article/details/104548354