【学习笔记】C/C++

1. 【C语言】C中的 scanf() 函数

该函数包含在头文件 <stdio.h> 或者 <cstdio> (在C++中使用时)

函数的返回值指的是

所输入的数据与格式字符串匹配的次数。

意思就是该函数返回已成功赋值的数据项数,出错时返回 EOF (End_of_File,是一个预定义常量,表示文件末尾,值为-1)

简单示例:

 1 #include <stdio.h>
 2 
 3 int main(void)
 4 {
 5     int a, b;
 6     int input = scanf("%d %d", &a, &b);
 7     printf("input = %d\n", input);
 8     
 9     return 0;
10 }

此时输入 1 2 

得到输出 input = 2 

上述结果表示正确匹配,若出现错误,如输入 1 a 

第二个值匹配失败,整型变量b无法得到字符‘a’,依旧是一个未赋值前不确定的值,则此时输出结果为 input = 1 

总之,我们可通过函数scanf( )返回值,来检测输入格式的正确性。

2. 【C++】C++类与对象

2.1 问题:我们需要将一个类的所有实例都保存在一个容器中,同时又不需要类的使用者进行其他的操作,该如何实现?

方法:在类中定义一个static类型的容器作为类的成员变量,构造对象时将对象的地址添加到容器中,析构时再将其从容器中删除。

 1 #include <iostream>
 2 #include <list>
 3 #include <algorithm>
 4 
 5 using namespace std;
 6 
 7 class MyClass {
 8 protected:
 9    int value_;
10 public:
11    static list<MyClass*> instances_;
12    MyClass(int val);
13   ~MyClass();
14    static void showList();
15 };
16 
17 list<MyClass*> MyClass::instances_;
18 
19 MyClass::MyClass(int val) {
20    instances_.push_back(this);
21    value_ = val;
22 }
23 
24 MyClass::~MyClass() {
25    list<MyClass*>::iterator p =
26       find(instances_.begin(), instances_.end(), this);
27    if (p != instances_.end())
28       instances_.erase(p);
29 }
30 
31 void MyClass::showList() {
32    for (list<MyClass*>::iterator p = instances_.begin();
33         p != instances_.end(); ++p)
34       cout << (*p)->value_ << endl;
35 }
36 
37 int main(int argc, char **argv) {
38    MyClass a(1);
39    MyClass b(10);
40    MyClass c(100);
41    MyClass::showList();    // 输出的结果为1,10,100
42    return 0;  
43 }

3. 生成随机数序列

3.1 C语言中 rand() 函数用来产生随机数,但这不是真正意义上的随机数,是伪随机数。

它是以一个数(通常称为种子)为基准推算出来的一系列数,当这系列数很大时,就会服从正态公布,从而相当于产生了随机数。

当计算机正常开机后,这个种子的值是定了的,除非你破坏系统,才能改变这个种子的值。

C++提供了 std::srand() 函数,其原型是 void srand( int a) ,利用 srand((unsigned int) (time(NULL)) 是一种产生不可预见的随机序列的方法,因为每一次运行程序的时间是不同的。

3.2 以当前时间为参数重置随机数种子

如果没调用 srand() ,你会发现你每次运行程序, rand() 得到的序列值是不变的,然后 srand 里参数相同时 rand() 得到的序列也将相同。

std::srand(time(0)) 的作用就是让得到的序列看上去更贴近随机的概念。

等有空把遇到的错误和更标准的方法总结一下。

 

4. 【C++】Lambda表达式

为何使用 Lambda ?

4.1 距离

很多人认为,让定义位于使用的附近很有用。这样就无需翻阅很多源代码,以了解函数。修改代码也很简单。

Lambda 表达式是不错的选择,而函数是不好的选择,因为不能内部定义其他函数,定义可能离使用的地方很远。函数符是个不错的选择,可以在函数内部定义类(包含函数符类)。

4.2 简洁

函数符代码要比l Lambda 代码更加繁琐,函数和 Lambda 的简洁程度相当。

4.3 效率

三种方法相对效率取决于编译器的内联。函数指针阻止了内联,因为编译器传统上不会内联其他地址被获取的函数,因为函数地址的概念意味着非内联函数。而函数符和 Lambda 通常不会阻止内联。

4.4 功能

Lambda 表达式可访问作用域内的任何动态变量,可以采用取值、引用的形式进行捕获。

 

5. 【C语言】C中 static 关键字

作用有两个,一是修饰变量,二是修饰函数。

  • (I) 用static修饰的变量,我们称之为静态变量。静态变量与全局变量一样,采用的是静态存储方式,存储在计算机的堆中。所修饰的变量包括全局变量和局部变量。

    • (a) 原本全局变量的作用域是整个源程序,包括源程序中的各个源文件。static修饰的全局变量是一个静态全局变量,作用域仅限于变量被定义的文件,在其他文件中不能引用,即使用 extern 声明也不行。从而起到了对其他源文件进行隐藏与隔离错误的作用,有利于模块化程序设计。
    • (b) 普通的局部变量存储在栈上,作用域是所在代码块或者函数体,超出范围则无法使用。

    static修饰的局部变量是静态局部变量,存储方式也从原来的栈中存放改为静态存储区存放。静态局部变量是在编译时赋初值的,且只赋初值一次,在程序运行时已有初值。以后在每次调用函数时就不再重新赋初值,而是保留上次函数调用结束时的值。

    这就明示了静态局部变量一般的使用场景:

    • 需要保留函数上一次调用结束时的值。
    • 如果初始化后,变量只会被引用而不会改变其值,则这时用静态局部变量比较方便,以免每次调用时重新赋值。
  • (II) static修饰的函数称为静态函数。

    此处静态的含义不是指存储方式,而是指对函数的作用域仅局限于本文件(所以又称内部函数)。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数,是否会与其它文件中的函数同名。

此外,定义在函数体内的static静态变量具有代码块作用域和空链接,实际上并不是函数的一部分。如果用调试程序单步运行该程序,会发现程序看起来跳过了那一步,因为静态变量和外部变量在程序调入内存时已经就位了,它不是运行时执行的语句,放在函数体内只是为了告诉编译器只有该函数可以看到该变量。

另外需要说明的一点是:在声明和定义全局变量时,只可以用常量表达式来初始化,不能使用变量。sizeof表达式被认为是常量表达式。

 

6. 【C语言】一般文本中的书写惯例

遵循《C和指针》的惯例,代码将严格以Courier New表示,代码的抽象描述用斜体Courier New表示。

如果你决定使用可选部分,它将严格以粗体Courier New表示,代码可选部分的描述将以粗斜体Courier New表示。缩进用空格而不是制表位Tab表示。

 

7.【C语言】C中的offsetof函数

offset 是在头文件<stddef.h>中实现的宏定义,展开为一个size_t值,以字节数来表示指定成员相对于类型type的结构开始处的偏移量,如果指定成员是一个位字段,属于undefined行为。

主要作用是返回结构体中的某个变量在内存中的偏移量,从结构体存储的起始位置开始算起。

通常调用格式如下,需要包含头文件stddef.h

// offsetof - offset of a structure member

#include <stddef.h>

size_t offsetof(type, member);

下面是在Linux源文件中的一种实现,供参考。

/*
 * @Filename: /usr/src/linux-headers-5.4.0-94-generic/include/linux/stddef.h
 */
#define offsetof(TYPE, MEMBER)  ((size_t)&((TYPE *)0)->MEMBER)

8.【C语言】《C Premier Plus》要点摘录

8.1 取值运算符

间接运算符(indirection operator)/取值运算符(dereferencing operator)*用于获取存储在被指向地址中的数值。

*和指针名之间的空格是可选的。通常在声明中使用空格,而在指向变量时将其省略。

一元运算符*++具有相同的优先级,自右向左进行结合。*p++ 相当于 *(p++)

8.2 C99标准新增的指定初始化方式

8.2.1 数组的指定初始化方式

首先提一下普通数组声明,声明数组时方括号内只能使用整数常量表达式,并且该表达式的值必须大于0。sizeof表达式被认为是一个整数常量,而在C语言标准中const值却不是整数常量。

在C99规定的指定初始化方式中需要注意的点如下所列

  • (1) 在初始化列表中使用带有方括号的元素下标可以指定某个特定的元素,如果多次对同一个元素进行初始化,则最后一次有效。
  • (2) 如果在一个指定初始化项目后跟有不止一个值,这些数值将用来对后续的数组元素进行初始化。
  • (3) 在初始化一个或多个元素后,未经初始化的元素将被置为0。
int arr[6] = {[5]=212}; // 把arr[5]初始化为212
int days[12] = {[4]=31, 30, 31, [1]=29}; // days[1]=29,days[4]=31,days[5]=30, days[6]=31
8.2.2 结构体的指定初始化方式

结构的指定初始化项目使用点运算符和成员名标识具体的元素。

struct book {
	char title[MAXTITL];
	char author[MAXAUTL];
	float value;
};

struct book gift = {
	.value = 25.99f,
	.author = "James Broadfool",
	.title = "Rue for the Toad"
};
8.2.3 结构体中的伸缩性数组成员

对于结构体,C99标准还新增了伸缩性数组的成员用法。

  • (1) 伸缩性数组必须是最后一个结构成员。
  • (2) 结构中必须至少还有一个其他成员。
  • (3) 伸缩性数组就像普通数组一样被声明,但方括号是空的。

C99的意图并不是让您声明struct类型的变量,而是希望您声明一个指向struct类型的指针,然后使用malloc()函数分配足够的空间,以存放结构的常规内容和伸缩性数组成员需要的任何额外空间。

像下面的例子展示的这样。

struct flex {
	int count;
	double avg;
	double scores[]; // 伸缩性数组成员
};

struct flex *pf;
pf = (struct flex *)malloc(sizeof(struct flex)+5*sizeof(double));

8.3 C语言存储类说明符关键字

C语言中有五个存储类说明符关键字:auto、register、static、extern、typedef,关键字typedef与内存存储无关,由于语法原因被归入此类。
特别要注意的是,不可以在一个声明中使用一个以上存储类说明符。
不能将其他任一存储类说明符作为typedef的一部分。

一个值可以同时是const和volatile,例如硬件时钟一般设定为不能由程序改变这一点使它成为const,但它被程序以外的代理改变这使它成为volatile。

const volatile int loc;
const volatile int *ploc;

与 const 有关的几个例子

int *p1;
const int *p2;
const int **pp2;
p1 = p2;    // 非法,不能把const指针赋值给非const指针,防止使用新指针改变const数据
p2 = p1;    // 合法,可以把非const指针赋值给const指针
pp2 = &p1;  // 非法,不能把非const指针赋值给const指针,pp2只能指向const int*类型
const int **pp2;
int *p1;
const int n = 13;
pp2 = &p1;  // 如果可以赋值,那么下面的情形将使得p1可以改变n中的值,显然是错误的!
*pp2 = &n;
*p1 = 10;   // 将会改变const n的值

8.4 切记不能对未初始化的指针取值

int *pt;
*pt = 5; //pt指向的位置是随机的,可能会覆盖程序数据或代码甚至导致程序崩溃。

char *p_str; // 对字符串也一样
scanf("%s", p_str); // 潜在危险!p_str为未初始化变量,地址可能是任意值,程序会把读到的字符串放在任何地方,这是一个容易被忽略的严重误用!

8.5 字符串操作

8.5.1 指针指向的字符串常量与字符串数组的区别
char str1[] = "hello world"; /*Line 1*/
char *pstr = "good morning"; /*Line 2*/
str1[0] = 'H';
pstr[0] = 'G';

puts(str1); /*Line 6*/// 定义为数组形式的得到修改后的结果
puts(pstr); /*Line 7*/// 定义为指针形式的得到的是修改前的结果!

第七行这种修改可能会导致内存访问错误,原因在于编译器可能选择内存中的同一个拷贝来表示所有相同的字符串文字。如果编译器允许把指针指向的某个字符元素进行修改的话,那将会影响到所有对这个字符串的使用,因此C标准不允许编译这样做。
建议的做法应该是初始化一个指向字符串的指针时使用const修饰符。

const char *pstr = "good luck";
8.5.2 字符串读写函数
  • gets()函数不检查预留存储区是否能够容纳实际输入的数据,多出来的字符简单地溢出到相邻的内存区。
  • fgets()函数会连带最后的换行符也读取并存储到字符串中
  • scanf()函数默认读取到的内容不包含最后的换行符
#define SIZE 200
char str[SIZE];
scanf("%s", str); // 以%s读取字符串时,以遇到的第一个非空白字符开始,读到下一个空白字符,不包括空白字符。如果使用如%10s格式指定长度则读取10个字符或遇到第一个空白字符时终止输入。

scanf("%*s", str); // 跳到下一个空白字符。忽略接下来输入的第一个字符串。这里的*字符是滞后赋值标志符,对于scanf函数而言*所在的域不计算在读取计数之中,也就是说scanf的返回值不包含这个域。

scanf("%1s", str); // 读取一个字符

对于不能自动处理输入时的末尾换行符的函数如scanf、fgets等,可以使用strchr()函数定位和删除换行符

#define SIZE 100
char line[SIZE];
fgets(line, SIZE, stdin);
char *find = strchr(line, '\n');
if (find) {
	find = '\0';
}

8.6 限定词关键字

限定词用来限制使用变量的方式。C语言中的限定词有三个:const, volatile, restrict

  • const变量在初始化后不能被修改。
  • 编译器不能假定一个volatile变量不被外部代理或者硬件更新所改变。
  • restrict限定的指针被理解为在特定的作用域中提供对一块内存的唯一访问接口。
/*
 * 此处的restrict即用来说明dst和src是访问它们分别指向的内存块的唯一方法,这意味着
 * 两个块之间不能重叠。返回目标地址
 */
void *memcpy(void *restrict dst, void *restrict src, size_t n);

/*
 * 从src指向的位置复制n个字节的数据到dst指向的位置。首先把dst的字节复制到一个临时位置,
 * 这样当源位置和目标位置有重叠时,仍然可以正确复制,返回目标地址
 */
void *memmove(void *dst, void *src, size_t n);

参考资料

[1] c++随机数生成 http://blog.sina.com.cn/s/blog_79ab4be10100uzrj.html

[2] C++产生随机数一直重复的问题 https://www.csdn.net/gather_22/MtzakgzsMTUtYmxvZwO0O0OO0O0O.html

[3] static变量及其作用,C语言static变量详解 http://c.biancheng.net/view/301.html

[4] C语static关键字—言最名不符实的关键字 http://c.biancheng.net/cpp/html/436.html

posted @ 2019-05-17 21:48  coffee_tea_or_me  阅读(248)  评论(0编辑  收藏  举报