using comma operator in the body of a loop - c

I'm trying to write a while loop in two different ways and noticed that using ',' in the condition twice doesn't work the way I think it does. I'm just curious about what actually happens. Here's the code :
#include <stdio.h>
int main()
{
int i = 0, lim = 1000, c;
char s[lim];
while (i < lim - 1, (c = getchar()) != '\n')
{
if (c != EOF)
{
s[i] = c;
}
else
{
break;
}
++i;
}
printf("%s\n", s);
return 0;
}
#include <stdio.h>
int main()
{
int i = 0, lim = 1000, c;
char s[lim];
while (i < lim - 1, (c = getchar()) != '\n', c != EOF)
{
s[i] = c;
++i;
}
printf("%s\n", s);
return 0;
}

Lets look at the while condition:
i < lim - 1, (c = getchar()) != '\n', c != EOF
The first part, i < lim -1 has no effect whatsoever. The second part will execute c = getchar(), but the comparison with '\n' is completely discarded. The return value of the whole condition is that of c != EOF. So the condition is equivalent to:
(c = getchar()) != EOF
What you can do is to change the comma operator for && instead.
i < lim - 1 && (c = getchar()) != '\n' && c != EOF

Comma does not make complex logical operations. You need to use logical operators instead

Related

Why the increment step inside the for loop is being executed, since not all conditions were met?

I'm studying C using the "C Programming Language" book, and I got into character arrays (section 1.9) from chapter 1. This is the getline() function it presents:
/* getline: read a line into s, return length */
int getline(char s[], int lim)
{
int c, i;
for (i = 0; i < lim-1 && (c = getchar()) != EOF && c != '\n'; ++i)
s[i] = c;
if (c == '\n') {
s[i] = c;
++i;
}
s[i] = '\0';
return i;
}
Take the word "Hi" as example.
In my point of view, s[1] should be equal to '\n', because the i should be only incremented when all conditions are met, however, when c is equal to '\n', the for loop exits, and i should contain a value of 1 and not 2.
So, why it is being incremented with only 2 of 3 conditions met?
I already tried using pre-increment operators and post-increments operators, and testing the i value inside certain lines of code.
The loop variable i is incremented at the end of the loop body. You can write your for-loop:
for(i = 0; i < lim-1 && (c = getchar()) != EOF && c != '\n'; i++)
s[i] = c;
as an equivalent while-loop:
i = 0;
while(i < lim-1 && (c = getchar()) != EOF && c != '\n') {
s[i] = c;
i++;
}
Also, I suggest you rename your function to not conflict with the exiting function of the same name. Consider swapping the arguments so you can express how the lim and s variables are related and use size_t instead of int as negative lim probalby doesn't make sense:
size_t my_getline(size_t lim, char s[lim]);
The loop condition to i < lim - 1 is wrong as may write 2 bytes after the loop which will overflow the buffer. If lim <= 0 you overflow the buffer. if lim < 1 you have undefined behavior as c is uninitialized. I suggest you handle the newline in the loop like this, and only write the '\0' if s has room for at least 1 byte:
size_t my_getline(size_t lim, char s[lim]) {
size_t i = 0;
while(i < lim - 1) {
int c = getchar();
if(c == EOF) break;
s[i++] = c;
if(c == '\n') break;
}
if(lim) s[i] = '\0';
return i;
}

When reusing character array I always get extra letter of previous value in the end

I am toying around with C language and I wanted to see how exactly null character \0 works. I have a character array initialized to length of 1000. I read text stream with getchar and place the characters into the array by iterating over the whole length of the array (1000).
After the loop I add null character to the end of stored characters. After this, I repeat the exact thing for second time. However when I print out the characters for 2nd time, I always get extra letter in the output.
STDIN input: aa
output aa
STDIN input b
output ba (I would expect it to be just b)
This is the code:
#include <stdio.h>
#define MAX 1000
int main() {
char line[MAX];
int i;
int c;
for (i = 0; (c = getchar()) != EOF && c != '\n' && i < MAX - 1; ++i) {
line[i] = c;
}
++i;
line[i] = '\0';
printf("%s\n", line);
for (i = 0; (c = getchar()) != EOF && c != '\n' && i < MAX - 1; ++i) {
line[i] = c;
}
++i;
line[i] = '\0';
printf("%s\n", line);
}
You are placing 0 one character too far.
for loop advanced the last char (to be populated in a loop). the line[i] is where that 0 should go. Remove ++i.
You're using pre-increment and i value get increased, Just remove ++i before assigning '\0'
Try this:
#define MAX 1000
int main() {
char line[MAX];
int i;
int c;
for (i = 0; (c = getchar()) != EOF && c != '\n' && i < MAX - 1; ++i) {
line[i] = c;
}
line[i] = '\0';
printf("%s\n", line);
for (i = 0; (c = getchar()) != EOF && c != '\n' && i < MAX - 1; ++i) {
line[i] = c;
}
line[i] = '\0';
printf("%s\n", line);
}
When you end a for loop as the ones you have written, you must think that i gets incremented just before the test that makes you to get out of the loop, so i actually points to the next character position.
This makes your lines
++i;
unnecessary, and you should just say:
line[i] = '\0';

How can I break up this into more than two functions?

How can I divide those two functions to more than two?
The functions read the file line by line.
An instruction will appear in a line in the file (at the end of each instruction there will be a newline
character). At the start of the running, the program will read the instruction line by line. Then it will
decode the required action and parameters and will call to perform the action with the appropriate
parameters.
I tried to put the foor loop, array, getc() to another function but it doesn't work.
void read_line(FILE *fp, char *orders, char *book_name, char *book_name_2, int *num)
{
int i = 0;
char c ;
*num = 0;
c = getc(fp);
while ((c != '\n') && (!feof(fp))) {
for (i = 0; (c != '$') && (c != '\n') && (!feof(fp)); i++) {
orders[i] = c;
c = getc(fp);
}
orders[i] = '\0';
if (c != '\n' && (!feof(fp))) {
fseek(fp, 3, 1);
c = getc(fp);
}
else break;
for (i = 0; (c != '$') && (c != '\n'); i++) {
book_name[i] = c;
c = getc(fp);
}
book_name[i] = '\0';
if (c != '\n' && (!feof(fp))) {
fseek(fp, 3, 1);
c = getc(fp);
}
else break;
if (strcmp(orders, "Rename ") != 0) {
for (i = 0; c != '\n'; i++) {
*num = (*num) * 10 + (c - '0');
c = getc(fp);
}
}
else {
for (i = 0; c != '\n'; i++) {
book_name_2[i] = c;
c = getc(fp);
}
book_name_2[i] = ' ';
book_name_2[i + 1] = '\0';
}
return;
}
}
Book* read_file_books(FILE *fp, Book *head, char *book_name, int *copies)
{
int i = 0;
char c ;
*copies = 0;
c = getc(fp);
while ((c != '\n') && (!feof(fp))) {
for (i = 0; (c != '$') && (c != '\n'); i++) {
book_name[i] = c;
c = getc(fp);
}
book_name[i] = '\0';
if (c != '\n') {
fseek(fp, 3, 1);
c = getc(fp);
}
else break;
for (i = 0; (c != '\n') && (!feof(fp)); i++) {
*copies = (*copies) * 10 + (c - '0');
c = getc(fp);
}
return add(head, book_name, *copies);
}
return head;
}
The most streamlined way of extracting a code block is to basically just copy the block to a function and then use pointers for variables that are declared outside that block. Let's take the for loop:
for (i = 0; (c != '$') && (c != '\n'); i++) {
book_name[i] = c;
c = getc(fp);
}
book_name[i] = '\0';
So, we will need access to i, c, book_name and fp. The simplest (but not the best) is this:
void foo(int *i, char *c, char *book_name, FILE *fp)
{
for (*i = 0; (*c != '$') && (*c != '\n'); (*i)++) {
book_name[*i] = *c;
*c = getc(fp);
}
book_name[*i] = '\0';
}
And then replace the for loop with:
foo(&i, &c, book_name, fp);
That's an easy procedure. But it gives you quite a lot of pointers. That's actually nothing wrong with the method itself. But you could get rid of some of them by considering in which scope you're declaring the variables. For instance, you can declare variables inside the for header, and you should unless you want to keep the last value. If you had done that, you could remove one parameter and get
void foo(char *c, char *book_name, FILE *fp)
{
for (int i = 0; (*c != '$') && (*c != '\n'); i++) {
book_name[i] = *c;
*c = getc(fp);
}
book_name[i] = '\0';
}
Note that I had to peek forward until the next usage if i to determine that this was safe. Your code is simply not written in a way that makes it suitable for block extraction.
You should also try to make the c variable local. Speaking of which, it should be an int and not a char. Read the documentation of getc to understand why.
Using for loop here is not wrong per se, but it's non idiomatic. For loops are typically used when you know how many times the loop should execute before the loop starts. When you want to loop until something happens, a while is more suitable. But what would be much better here is do-while. Because, when reading files, you want to try to read, and then check if you succeeded. You are technically doing that, but what makes this code hard to extract is that each block ends with reading a character that the next loop should process.
To correct this, we need to start from the beginning. First remove the very first instance to getc. Also, remove the conditions from the loop headers.
while (1) {
i=0;
// Each block takes care of 100% of their input
while((c = getc(fp)) != '$' && c != '\n' && c != EOF) {
orders[i] = c;
i++;
}
orders[i] = '\0';
// Negated your condition to make a cleaner if and get rid of else
if (!(c != '\n' && (!feof(fp)))) break;
fseek(fp, 3, 1);
...
The main thing I have corrected here is that every code block starts from scratch. It does not care about what happened before. In your code, you always started by checking the values from the previous blocks. This change makes it MUCH easier to extract code. In most cases when people ask how to extract code, the question they really should have asked is to how to change the code so that extraction becomes trivial.
Also, read Why is while(!feof(fp)) always wrong?

I am getting this warning "comparison of constant ‘10’ with boolean expression is always true"

I am unable to escape from the loop by entering newline.
int input(char str[], int n)
{
int ch, i = 0;
while ((ch == getchar()) != '\n')
if (i < n)
str[i++] = ch;
str[i] = '\0';
return i;
}
Just replace == with = because you want to assign getchar() to ch and not compare it.
int input(char str[], int n)
{
int ch, i = 0;
while ((ch = getchar()) != '\n'){
if (i < n)
str[i++] = ch;
}
str[i] = '\0';
return i;
}
Yo forgot { symbols in your code. Any control structure such while and if of more than one instruction line should be coded like this :
while(condition){
instruction 1;
instruction 2;
...
}
By the way your i is always 0 and your n does not change within your function.
EDIT: The reason you obtain your warning have been answered by someone else.
(ch == getchar())
You use == instead of =.
== is used as boolean operator for checking if the left hand operand equals right hand one.
Thus, the loop will never terminate because you compare 0 or 1 (which is the result of (ch == getchar()) with \n whose ASCII value is 10.
That´s what the error is saying.
= is used for assignment and required to assign the character fetched by getchar() to ch.
This is the corrected version:
while ((ch = getchar()) != '\n') {
if (i < n)
str[i++] = ch;
}

C - Rewriting a while-loop in a for-loop with if statements

--beware of horrible newbie code ahead--
I'm trying to rewrite this simple counting program from while to for.
int c, nc;
nc = 0;
while ((c = getchar()) != EOF) {
if (c != '\n')
++nc;
}
}
printf("%d\n", nc);
this outputs example->8.
so far I tried this couple of examples:
int c, nc;
for (nc = 0; ((c = getchar()) != EOF && (c = getchar()) != '\n'); ++nc)
;
printf("%d", nc);
and
int nc;
for (nc = 0; getchar() != EOF; ++nc)
if (getchar() == '\n')
--nc;
printf("%d", nc);
both this attempts lead to strange output like example->3 or a->0, also the program doesn't "wait" anymore for a break after receiving its input, it just shows the output and close itself.
I would like to know what's happening here, because as I see it I just inserted (quite clumsily.. ) an if check and can't seem to explain what's happening..
You are calling getchar() twice
for (nc = 0; getchar() != EOF; ++nc)
if (getchar() == '\n')
--nc;
printf("%d", nc);
try this instead
int chr;
int nc;
chr = fgetc(stdin);
for (nc = 0 ; chr != EOF ; nc += (chr == '\n') ? 0 : 1)
chr = fgetc(stdin);
printf("%d\n", nc);
getchar() which is equivalent to fgetc(stdin) reads a character from the input stream stdin, once you read that character you have to process it because it's removed from the stream, so calling the function twice will remove two characters from stdin and hence your count will be wrong.
So it doesn't matter how you write the for loop, what is important is to call getchar() once per iteration, for example this could work
int chr;
int nc;
for (nc = 0 ; ((chr = fgetc(stdin)) != EOF) ; nc += (chr == '\n') ? 0 : 1);
printf("%d\n", nc);
or this
int chr;
int nc;
for (nc = 0 ; ((chr = fgetc(stdin)) != EOF) ; )
{
if (chr != '\n')
nc += 1;
}
printf("%d\n", nc);
note that x = (condition) ? value : another_value is called the ternary operator, and is equivalent to
if (condition)
x = value;
else
x = another_value;
This is one equivalent way, although I'm not really happy about having to initialize c to \n. (the reason for this is that the get_char is performed in the post step:
int c, nc;
for (c = '\n', nc = 0; c != EOF; c = getchar()) {
if (c != '\n')
++nc;
}
}
printf("%d\n", nc);
Another way is:
int c, nc;
c = getchar();
for (nc = 0; c != EOF; c = getchar()) {
if (c != '\n')
++nc;
}
}
printf("%d\n", nc);
It can be done as shown below
int nc=0;
char ch;
for (; (ch =getchar()) != EOF; )
{
if (ch != '\n')
nc++;
}
printf("Count = %d\n",nc);
The initialization and increment are not mandatory they can be skipped as shown in the above code.So by doing this we are achieving the same as we do with your while() loop

Resources