一、相似关键字、函数等的区分
1.int *p[4] 与 int (*p)[4]的区别
1.int *p[4]
int *p[4]是包含四个元素的数组,每个元素都是一个指向int变量的指针,例如:
int a = 1, b = 2, c = 3, d = 4;
int *p[4] = {&a, &b, &c, &d};
这样,p[0] 将指向整数变量 a,即(*p[0]=a);p[1]将指向整数变量 b,即(*p[0]=a),依此类推。
2.int (*p)[4]
int (*p)[4]是指向一维数组的一个指针,一维数组里每个元素都是一个包含四个int变量的数组(可以用来表示n行4列的二维数组),例如:
int matrix[3][4] = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
int (*p)[4] = matrix;
其中,*p[0]=matrix[0][0],*(p[0]+1)=matrix[0][1],*p[1]=matrix[1][0],*(p[0]+1)=matrix[0][1],以此类推。
2.const char *、char const*、char *const三者的区别
对于const char *:
对于const char *s来说,const char*是指向常量的指针,而不是指针本身为常量,可以不被初始化.该指针可以指向常量也可以指向变量,只是从该指针的角度而言,它所指向的是常量。*s是不变的,s是可以改变的,const限定的*s。s被一个解引用运算符修饰,故s是个普通的指针,可以修改,但是s所指向的数据(即*s)由于const的修饰而不可通过指针s去修改。
对于char const*:
和const char *是一个意思,都是const限定的*s。
对于const char *:
char *const s声明(*const s),(*const s)是char类型的。s被一个解引用运算符和一个const关键词修饰,故s是个不可修改的指针,但可通过指针s去修改s所指向的数据(即*s)。
总结:
const char *s中,指针*s被const和char修饰,*s的值不能改变而s的值可以改变,也就是可以改变指向但不能修改数据,即:const char (*s),char const *s也是同理。而在char *const s中,s被const修饰,即s的值不能改变而*s的值可以改变,也就是可以修改数据但不能改变指向,即char (*const s)。
如果想设置一个数据和指向都不能被修改的指针,则声明为const char * const s即可。
3.<cstring>和<string.h>的区别
<cstring>
和 <string.h>
实际上是 C++ 和 C 两个语言中用于字符串操作的头文件,它们有一些微小的区别:
- Namespace:
<cstring>
是 C++ 标准库的一部分,其中的函数都被定义在std
命名空间中。例如,C++ 中的字符串比较函数是std::strcmp
。<string.h>
是 C 标准库的头文件,其中的函数都在全局命名空间中。例如,C 中的字符串比较函数是strcmp
。
- C++ Compatibility:
<cstring>
提供了 C 函数的 C++ 版本,这些函数都位于std
命名空间中,以支持 C++ 的标准库和命名空间特性。<string.h>
是 C 标准库的一部分,不一定提供与 C++ 标准库的兼容性。
在 C++ 中,你可以使用 <cstring>
,并将函数放在 std
命名空间中。如果你正在编写 C 代码,你应该使用 <string.h>
。
4.cin()和scanf()的区别
最主要的区别是:对于输入的传入方式不同
对于cin()而言,可以用下列方式来输入a的值:
int* p, a;
p = &a;
cin >> a;
//或者 cin >> *p;
而对于sacnf()来说,需要传入变量的地址或者指向变量的指针:
int a, *p;
p = &a;
scanf("%d", &a);
//或者 scanf("%d", p);
cin
和 scanf
是用于输入的两个不同的函数,一个用于 C++,另一个用于 C。以下是它们之间的其他区别:
- 语言差异:
cin
是 C++ 中的标准输入流对象scanf
是 C 中的标准库函数- 两者都是用于从标准输入设备(通常是键盘)读取数据。
- 类型安全性:
cin
是类型安全的,它会进行类型检查,确保输入的数据类型与变量的类型相匹配。如果类型不匹配,cin
会产生错误。scanf
对类型的检查相对较弱,需要开发者确保格式化字符串中的类型与传递给函数的参数类型相匹配。如果不匹配,可能导致不确定的行为或错误。
- 格式化输入:
cin
不使用格式化字符串,而是根据变量的类型自动解析输入。scanf
使用格式化字符串来指定输入的格式,必须确保格式化字符串与输入数据的格式相匹配。
- 可读性:
cin
更直观,更易读,特别是在处理复杂数据类型和对象时。scanf
的格式化字符串可能会变得相对复杂,尤其是在处理不同的数据类型和格式时。
在 C++ 中,通常推荐使用 cin
,因为它提供了更好的类型安全性和可读性。而在 C 中,可以使用 scanf
进行输入。然而,如果你在 C++ 中工作,也可以使用 cin
和其他相关的 C++ 输入方法。
二、部分函数的解析理解
1.strcmp()函数
#include<cstring>
char a[10], b[10];
const char* s1;
const char* s2;
s1 = a;
s2 = b;
int n = strcmp(s1, s2);
strcmp()是字符串之间的比较函数,将字符串 str1 与 字符串 str2 进行比较,一般借助char *s指针来进行逐个对比,为了达到能够修改指向的同时防止修改*s的值,通常会写成const char *s来实现。
此函数开始比较每个字符串的第一个字符。如果它们彼此相等,则继续使用下一个字符进行比较,直到字符不同或达到终止空字符。
#include<iostream>
#include<cstring>
using namespace std;
int main()
{
char a[10] = "AAbDeG", b[10] ="AAbdEG";
const char* s1;
const char* s2;
s1 = a+1;
s2 = b+1;
int x = strcmp(s1, s2);
s1 += 2;
s2 += 2;
int y = strcmp(s1, s2);
s1 += 1;
s2 += 1;
int z = strcmp(s1, s2);
cout << x << " " << y << " " << z;
return 0;
}
该程序的输出结果是 –1 –1 1 (如果仍不明白可以自己修改数值演绎推理一下)
strcmp()函数包含在<cstring> 头文件中,该头文件还包含其他用于字符串操作的函数声明,如 strcpy()、strlen()等。
2.printf()函数
printf()
是一种常见的输出函数,通常用于将格式化的文本输出到控制台或文件。它在许多编程语言中都有对应的实现,比如C语言、C++、Java等。
在C语言中,printf()
函数的基本语法如下:
#include <stdio.h>
int printf(const char *format, ...);
这里是一些关于 printf()
函数的重要概念和用法:
-
格式化字符串:
printf()
函数的第一个参数是一个格式化字符串,其中包含了两种类型的内容,普通字符和格式化占位符。普通字符直接输出,而格式化占位符指定了输出的数据类型和格式。printf("Hello, %s!\n", "World");
在上面的例子中,
%s
是一个字符串的占位符,它将被后面的字符串 “World” 替代。 -
占位符:
printf()
函数支持多种占位符,常见的包括:%d
: 以十进制整数形式输出%f
: 以浮点数形式输出%c
: 以字符形式输出%s
: 以字符串形式输出%x
: 以十六进制形式输出
int num = 42; printf("The answer is %d\n", num);
-
转义字符: 在格式化字符串中,可以使用转义字符来表示一些特殊字符,比如换行符
\n
、制表符\t
等。printf("This is a new line.\n");
-
多个参数:
printf()
函数支持多个参数,可以在格式化字符串中使用多个占位符,并在函数调用时提供相应数量的参数。int a = 5, b = 10; printf("The sum of %d and %d is %d\n", a, b, a + b);
-
返回值:
printf()
函数返回成功打印的字符数,如果发生错误则返回负值。这个返回值可以用于检查是否成功执行了打印操作。int characters_printed = printf("Hello, World!\n"); printf("Printed %d characters.\n", characters_printed);
总的来说,printf()
是一个强大而灵活的输出函数,它使得程序员能够以各种方式格式化和输出文本。在实际应用中,理解并熟练使用 printf()
函数是编程中的基本技能之一。
3.gets()函数与fgets()函数
gets()
函数是 C 语言标准库中的一个函数,用于从标准输入(通常是键盘)读取一行文本,并将其存储在一个字符数组中。然而,需要注意的是,gets()
函数存在严重的安全性问题,因为它不提供对输入缓冲区的边界检查,可能导致缓冲区溢出。
由于安全性问题,C11标准中已经弃用了 gets()
函数,并建议使用更安全的替代函数 fgets()
来进行输入操作。
如果你需要读取一行文本,建议使用 fgets()
函数,如下所示:
#include <stdio.h>
int main() {
char buffer[100]; // 定义一个字符数组来存储输入的文本
printf("Enter a line of text: ");
// 使用fgets()函数来安全地读取一行文本
fgets(buffer, sizeof(buffer), stdin);
// 打印输入的文本
printf("You entered: %s", buffer);
return 0;
}
fgets()han函数的特点:
- 相对安全:
fgets()
是相对较安全的函数,因为它允许你指定最大读取的字符数,从而防止缓冲区溢出。 - 包含换行符:
fgets()
会将换行符(\n
)包含在读取的字符串中,因此你需要额外的处理来去掉换行符,如果不需要的话。 - 通用性:
fgets()
可以用于从文件、标准输入等不同的输入流中读取数据,而gets()
只能从标准输入中读取。
请注意fgets
在读取字符串时也会包含换行符 \n
,因此你可能需要在读取后的字符串中去掉换行符,以免影响后续的处理。可以使用 strlen
函数来计算字符串长度,并在最后一个非空字符之后的位置替换为 '\0'
。
//计算字符串的长度
len = strlen(buffer);
//把换行符'\n'换成'\0'
if (buffer[len - 1] == '\n')
buffer[len - 1] = '\0';
三、如何表示转义序列
1.八进制转义序列
在C++中,八进制转义序列使用\
(反斜杠)后面跟随1到3个八进制数字来表示。每个八进制数字可以是0到7之间的任意数字。
具体的语法为 \0xxx
,其中 xxx
是一个八进制数字序列。
例如:
\0
表示空字符(ASCII码为0)\07
表示ASCII码为7的字符\012
表示ASCII码为12的字符
#include<iostream>
using namespace std;
int main()
{
char *s = "\ta\017bc"; // 字符串初始化
for (; *s != '\0'; s++)
printf("*"); // 在每个字符上打印一个星号
return 0;
}
在这段代码中,声明了一个字符指针 char *s
,并初始化它为一个包含转义字符和八进制转义序列的字符串 "\ta\017bc"
。然后使用一个循环遍历字符串中的每个字符,并在每个字符上打印一个星号。
其中,字符串 "\ta\017bc"
包含四个字符:\t
表示水平制表符(Tab),\017
是一个八进制转义序列,表示一个特定的字符,以及字符 'b'
和 'c'
。打印出来的结果为 ******(6个*)
2.十六进制转义序列
在 C++ 字符串中,你可以使用十六进制转义序列来表示一个字符。十六进制转义序列的语法为 \x
后面跟着一个或多个十六进制数字。以下是一个简单的示例,演示了如何在 C++ 字符串中使用十六进制转义序列:
#include <iostream>
using namespace std;
int main()
{
// 使用十六进制转义序列表示字符串
const char* hexString = "\x48\x65\x6C\x6C\x6F"; // 表示 "Hello"
// 输出字符串
cout << hexString << endl;
return 0;
}
在上面的例子中,\x48
表示字符 ‘H’ 的 ASCII 码(十六进制值为 0x48),\x65
表示 ‘e’,以此类推。
请注意,十六进制转义序列只能用于字符常量或字符串字面值,而不能直接用于字符变量。如果你想将一个字符的十六进制值存储在变量中,你可以使用相应的整数值,然后将其转换为字符。
#include <iostream>
using namespace std;
int main() {
// 将十六进制值存储在字符变量中
char hexChar = 0x48; // 表示字符 'H'
// 输出字符
cout << hexChar << endl;
return 0;
}
在这个例子中,0x48
是字符 ‘H’ 的 ASCII 码的十六进制值。
四、部分关键字的解析
1.static是什么?
static
是一个关键字,它在不同的上下文中有不同的含义。以下是 static
的几种常见用法:
-
在全局变量中的使用:
当
static
用于全局变量时,它表示该变量具有文件作用域,即在声明它的文件中可见,其他文件无法访问。这种用法在限制变量的作用域,以及防止与其他文件中相同名称的全局变量发生冲突时很有用。// 在文件中声明一个具有静态全局作用域的变量 static int globalVar = 10;
-
在函数中的使用:
当
static
用于函数中的局部变量时,它表示该变量在函数调用之间保持其值,并且只初始化一次。这使得该变量在整个程序生命周期内存在,而不是在每次函数调用时都被创建和销毁。void exampleFunction() { // 静态局部变量,只初始化一次,保持值在函数调用之间 static int localVar = 0; localVar++; cout << "localVar: " << localVar << endl; }
-
在类中的使用:
在类中使用 static
可以创建静态成员变量和静态成员函数。静态成员变量是类的所有对象共享的,而静态成员函数与特定对象的实例无关,可以通过类名调用。
class ExampleClass {
public:
// 静态成员变量,所有对象共享
static int staticVar;
// 静态成员函数,与特定对象无关
static void staticFunction()
{
cout << "Static function called." << endl;
}
};
// 在类外初始化静态成员变量
int ExampleClass::staticVar = 0;
-
在文件中的使用:
当
static
用于文件中的函数时,它表示该函数的可见性限制在当前文件中,防止其他文件调用这个函数。// 在文件中声明一个具有静态作用域的函数 static void fileStaticFunction() { cout << "File static function called." << endl; }
总的来说,static
根据上下文的不同,可以表示不同的语义和用法。
2.auto是什么?
auto
是 C++11 引入的关键字之一,用于进行类型推导,使得编译器能够根据初始化表达式的类型推断出变量的类型,从而简化代码书写。使用 auto
可以减少冗长的类型声明,尤其是在涉及复杂类型、模板类型或迭代器时,它能够使代码更加简洁和易读。在大多数情况下,编译器能够自动推导出正确的类型。
以下是一些使用 auto
的例子:
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 使用 auto 推导基本类型
auto x = 5; // 推导为 int
auto y = 3.14; // 推导为 double
auto flag = true; // 推导为 bool
// 使用 auto 推导复杂类型
vector<int> numbers = {1, 2, 3, 4, 5};
for (auto it = numbers.begin(); it != numbers.end(); ++it) {
cout << *it << " "; // it 推导为 vector<int>::iterator
}
// 使用 auto 推导 lambda 表达式的返回类型
auto add = [](int a, int b) { return a + b; };
cout << add(3, 4) << endl; // 推导为 int
return 0;
}
需要注意的是,auto
并不是一种动态类型,而是在编译时进行类型推导。因此,编译器会在初始化时根据表达式的类型确定变量的类型。虽然 auto
提供了方便,但在一些情况下可能导致可读性降低,因此在选择使用时需要权衡。
五、C++中的宏定义
在C++中,宏定义是一种预处理器指令,用于在编译之前替换源代码中的文本。宏定义是通过#define
关键字来创建的。宏定义的基本语法如下:
#define 宏名称 替换文本
在这里,宏名称是你为宏定义的标识符,替换文本是在程序中找到该宏名称时将其替换的文本。
例如,以下是一个简单的宏定义:
#define PI 3.14159
在程序的其他地方,如果你使用了PI
,预处理器将在编译之前将其替换为3.14159
。
宏定义不仅可以用于简单的文本替换,还可以用于参数化的宏。例如:
#define SQUARE(x) ((x) * (x))
在这里,SQUARE
是一个接受一个参数的宏,它返回该参数的平方。当你在程序中使用SQUARE(5)
时,预处理器将把它替换为(5 * 5)
。
宏定义的标识符可以包括字母、数字和下划线,但不能以数字开头。所以,#define IBM_PC
是一个合法的宏定义。请注意,宏定义是区分大小写的,因此IBM_PC
和ibm_pc
被视为不同的标识符。在宏定义的标识符中使用大写字母,以便清晰地区分宏定义和普通变量或函数名。所以,#define IBM_PC
是一个良好的宏定义,因为它使用了大写字母,并且清晰地表示它是一个宏定义。
宏定义的使用要谨慎,因为它们仅仅是简单的文本替换,可能会导致意外的错误,特别是在宏参数周围没有使用括号时。因此,一些程序员更喜欢使用内联函数或常量来替代宏定义,以避免潜在的问题。
六、函数相关问题
1.对于数组的传参问题
若使用一维数组名作为函数实参,则以下正确的说法是( )
A)必须在主调函数中说明此数组的大小
B)实参数组类型与形参数组类型可以不匹配
C)在被调函数中,不需要考虑形参数组的大小
D)实参数组名与形参数组名必须一致
正确的说法是:
C) 在被调函数中,不需要考虑形参数组的大小
当你将一维数组名作为函数的参数传递时,只是将数组的地址传递给了函数。在被调函数中,可以通过这个地址访问数组的元素,而不需要知道数组的大小。因此,被调函数不需要考虑形参数组的大小。
选项 A 是不正确的,因为在主调函数中说明数组的大小是一个好的实践,但在一些情况下,可以不声明数组的大小。
选项 B 也是不正确的,因为实参数组类型与形参数组类型必须匹配。C++ 中并不会进行自动的类型转换,所以它们需要一致。
选项 D 也是不正确的,因为实参数组名与形参数组名可以不一致。函数声明和定义时只需要指定形参的类型,而不需要给出具体的参数名。
当将一维数组名作为函数参数传递时,下面是一个简单的例子:
#include <iostream>
using namespace std;
// 函数声明,形参数组的大小不需要指定
void printArray(int arr[], int size);
int main()
{
const int Size = 5;
int Array[Size] = { 1, 2, 3, 4, 5 };
// 调用函数,将数组名作为参数传递
printArray(Array, Size);
return 0;
}
// 函数定义,形参数组的大小仍然不需要指定
void printArray(int arr[], int size)
{
cout << "Array elements: ";
for (int i = 0; i < size; ++i)
cout << arr[i] << " ";
cout << endl;
}
在这个例子中,printArray
函数接受一个整型数组的地址和数组的大小作为参数,并打印数组的元素。在 main
函数中,创建了一个大小为 5 的整型数组 Array
,然后调用 printArray
函数,将数组名和大小传递给函数。在 printArray
函数内,通过数组的地址和大小可以正确地访问数组的元素。
已有以下数组定义和f函数调用语句,则在f函数的说明中,对形参数组array的正确定义方式为( )
int a[3][4];
f(a);
A)f(int array[][6])
B)f(int array[3][])
C)f(int array[][4])
D)f(int array[2][5])
对于给定的数组定义 int a[3][4];
和函数调用 f(a);
,正确的形参数组定义方式应该是:
C) f(int array[][4])
在C++中,当你将多维数组作为函数参数传递时,只需要指定第一维的大小,而其他维的大小可以在函数参数中省略。因此,对于二维数组 int a[3][4];
,形参数组的定义应该是 f(int array[][4])
。这样在函数内部,你可以通过这个形参数组访问二维数组的元素。
不幸的是,C++中对于多维数组的函数参数,不能将第一维的大小省略而保留第二维的大小。因此,f(int array[3][])
是不正确的写法。在C++中,多维数组的函数参数应该是 f(int array[][4])
,省略第二维的大小,而不是省略第一维。这样,编译器才能正确地理解数组的结构。
若用数组名作为函数调用的实参,传递给形参的是( )
A)数组的首地址
B)数组第一个元素的值
C)数组中全部元素的值
D)数组元素的个数
A) 数组的首地址
当你使用数组名作为函数调用的实参时,传递给形参的是数组的首地址。在C++中,数组名本身就是数组的首地址,它存储了数组元素的起始位置。通过传递数组的首地址,函数可以访问整个数组的元素。
2.函数的定义、声明与调用
以下错误的描述是( )
函数调用可以
A)出现在执行语句中
B)出现在表达式中
C)作为函数的实参
D)作为函数的形参
D) 作为函数的形参
函数调用不能作为函数的形参。在C++中,函数的形参是用于接收传递给函数的实际参数的变量,而实际参数可以是常量、变量、表达式等,但不能直接是一个函数调用。函数调用只能出现在执行语句中、表达式中或作为函数的实参。
以下正确的描述是( )
A)函数的定义可以嵌套,但函数的调用不可以嵌套
B)函数的定义不可以嵌套,但函数的调用可以嵌套
C)函数的定义和函数的调用均不可嵌套
D)函数的定义和函数的调用均可以嵌套
B) 函数的定义不可以嵌套,但函数的调用可以嵌套
在C++中,函数的定义通常不能嵌套在其他函数中。每个函数应该在全局作用域或类的成员函数中定义。但是,函数的调用可以在其他函数内嵌套调用,形成调用链。
下面函数调用语句含有实参的个数为( )
fun((exp1,exp2),(exp3,exp4,exp5));
A)1 B)2 C)4 D)5
在这个函数调用语句中,看起来像是传递了两组参数,但实际上使用了逗号运算符。逗号运算符会返回最后一个表达式的值。因此,实际上相当于以下形式:
fun(exp2,exp5);
函数隐含类型:
C语言允许函数值类型缺省定义,此时该函数值隐含的类型是( B )
A)float型
B)int型
C)long型
D)double型
对于函数的定义:
以下正确的函数定义形式是( )
A)double fun(int x,int y)
B)double fun(int x;int y)
C)double fun(int x,int y);
D)doubel fun(int x;int y);
正确的函数定义形式是:
A) double fun(int x, int y)
在C++中,函数定义应该包含返回类型、函数名、参数列表。正确的参数列表形式是用逗号 ,
分隔参数的类型和名称。所以,正确的形式是 double fun(int x, int y)
。而C)double fun(int x,int y);是函数的声明,仅比函数的定义多一个分号。
七、运算符优先级问题
在判断语句中&&拥有比||更高的优先级
//对于年份进行判断,是不是闰年
bool run(int year)
{
if(year%4==0&&year%100!=0||year%400==0)
return true;
else return false;
//其中判断语句等价于((year%4==0&&year%100!=0)||year%400==0)
}
对于 if ( … ) … else …语句中,可以用a > b? c : d这种简单形式来替换
即判断a>b是否为true,如果满足,则语句块的值为c,如果如果不满足(也就是a>=b),则这个语句块的值就是d,例如:
a = 5 < 3? 7:16;
//a的值即为16
接下来一道稍微复杂的例题:
若有int w=1,x=2,y=3,z=4;则表达式w<x?w:y<z?y:z的值是( )
让我们逐步解析这个表达式 w<x?w:y<z?y:z
:
-
首先,检查
w<x
,由于w
的值是1,x
的值是2,所以条件为true。因此,表达式的值为w
,即1。 -
如果第一个条件为false,我们会检查
y<z
,由于y
的值是3,z
的值是4,这个条件为true。因此,表达式的值为y
,即3。
因此,整个表达式 w<x?w:y<z?y:z
的最终值为1,因为第一个条件为true。
以上就是我在期末复习c++过程中遇到的问题以及查阅了部分资料,有问题大家可以提出来在评论区探讨